import { eq } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import {
  isImmutableFieldError,
  isNotPurchasedError,
  isReviewsConfigError,
  isReviewValidationError,
  ReviewValidationError,
} from './errors.js'
import { pushReviewsSchema } from './migrate.js'
import { review, reviewsSchema } from './schema.js'
import { submitReview } from './submit.js'
import {
  createFakeVerifiedPurchasePort,
  noOpAutoApproveModerationAdapter,
} from './testing/index.js'

const PRODUCT_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
const USER_ID = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
const PURCHASE_ID_1 = 'purch-0000-0000-0000-000000000001'
const PURCHASE_ID_2 = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc'

async function freshDb() {
  const db = createPgliteClient({ schema: reviewsSchema })
  await pushReviewsSchema(db)
  return db
}

async function rowCount(db: Awaited<ReturnType<typeof freshDb>>) {
  const rows = await db.select().from(review)
  return rows.length
}

const purchaserPort = createFakeVerifiedPurchasePort({ purchased: true })

describe('submitReview', () => {
  it('submit-validates-rating — out-of-range ratings throw and write nothing', async () => {
    const db = await freshDb()

    await expect(
      submitReview(
        db,
        { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 0, purchaseId: PURCHASE_ID_1 },
        { verifiedPurchase: purchaserPort },
      ),
    ).rejects.toSatisfy((e) => isReviewValidationError(e) && e.field === 'rating')

    await expect(
      submitReview(
        db,
        { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 6, purchaseId: PURCHASE_ID_1 },
        { verifiedPurchase: purchaserPort },
      ),
    ).rejects.toBeInstanceOf(ReviewValidationError)

    expect(await rowCount(db)).toBe(0)
  })

  it('submit-no-port-throws — missing verifiedPurchase port → ReviewsConfigError (fail-closed)', async () => {
    const db = await freshDb()

    await expect(
      submitReview(
        db,
        { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 4, purchaseId: PURCHASE_ID_1 },
        // @ts-expect-error — deliberate missing port; runtime must throw before any write
        {},
      ),
    ).rejects.toSatisfy((e) => isReviewsConfigError(e))

    expect(await rowCount(db)).toBe(0)
  })

  it('submit-gate-blocks-non-purchaser — NotPurchasedError and zero rows (A4)', async () => {
    const db = await freshDb()

    await expect(
      submitReview(
        db,
        {
          productId: PRODUCT_ID,
          userId: USER_ID,
          vendorId: null,
          rating: 4,
          body: 'nope',
          purchaseId: PURCHASE_ID_1,
        },
        {
          verifiedPurchase: createFakeVerifiedPurchasePort({ purchased: false }),
        },
      ),
    ).rejects.toSatisfy((e) => isNotPurchasedError(e))

    expect(await rowCount(db)).toBe(0)
  })

  it('submit-default-pending — purchaser without moderation adapter lands pending (D3)', async () => {
    const db = await freshDb()

    const created = await submitReview(
      db,
      {
        productId: PRODUCT_ID,
        userId: USER_ID,
        vendorId: null,
        rating: 4,
        body: 'ok',
        purchaseId: PURCHASE_ID_1,
      },
      { verifiedPurchase: purchaserPort },
    )

    expect(created.status).toBe('pending')
  })

  it('submit-autoapprove — no-op auto-approve adapter publishes approved', async () => {
    const db = await freshDb()

    const created = await submitReview(
      db,
      {
        productId: PRODUCT_ID,
        userId: USER_ID,
        vendorId: null,
        rating: 5,
        body: 'love it',
        purchaseId: PURCHASE_ID_1,
      },
      {
        verifiedPurchase: purchaserPort,
        moderation: noOpAutoApproveModerationAdapter,
      },
    )

    expect(created.status).toBe('approved')
  })

  it('submit-upsert-edits-not-duplicates — same buyer/product updates one row', async () => {
    const db = await freshDb()

    const first = await submitReview(
      db,
      {
        productId: PRODUCT_ID,
        userId: USER_ID,
        vendorId: null,
        rating: 3,
        body: 'first',
        purchaseId: PURCHASE_ID_1,
      },
      { verifiedPurchase: purchaserPort },
    )

    const second = await submitReview(
      db,
      {
        productId: PRODUCT_ID,
        userId: USER_ID,
        vendorId: null,
        rating: 5,
        body: 'second',
        purchaseId: PURCHASE_ID_1,
      },
      { verifiedPurchase: purchaserPort },
    )

    expect(second.id).toBe(first.id)
    expect(second.rating).toBe(5)
    expect(second.body).toBe('second')
    expect(await rowCount(db)).toBe(1)
  })

  it('submit-edit-resets-status — approved review re-submitted without moderation returns pending (D5)', async () => {
    const db = await freshDb()

    const first = await submitReview(
      db,
      {
        productId: PRODUCT_ID,
        userId: USER_ID,
        vendorId: null,
        rating: 5,
        body: 'clean',
        purchaseId: PURCHASE_ID_1,
      },
      {
        verifiedPurchase: purchaserPort,
        moderation: noOpAutoApproveModerationAdapter,
      },
    )
    expect(first.status).toBe('approved')

    const edited = await submitReview(
      db,
      {
        productId: PRODUCT_ID,
        userId: USER_ID,
        vendorId: null,
        rating: 5,
        body: 'edited abuse',
        purchaseId: PURCHASE_ID_1,
      },
      { verifiedPurchase: purchaserPort },
    )

    expect(edited.status).toBe('pending')
    expect(edited.createdAt.getTime()).toBe(first.createdAt.getTime())
    expect(edited.updatedAt.getTime()).toBeGreaterThanOrEqual(first.updatedAt.getTime())
  })

  it('rejects a different vendorId on upsert with ImmutableFieldError', async () => {
    const db = await freshDb()
    const vendorA = 'vendor-a'

    await submitReview(
      db,
      {
        productId: PRODUCT_ID,
        userId: USER_ID,
        vendorId: vendorA,
        rating: 4,
        body: 'first',
        purchaseId: PURCHASE_ID_1,
      },
      { verifiedPurchase: purchaserPort },
    )

    await expect(
      submitReview(
        db,
        {
          productId: PRODUCT_ID,
          userId: USER_ID,
          vendorId: 'vendor-b',
          rating: 4,
          body: 'swap',
          purchaseId: PURCHASE_ID_1,
        },
        { verifiedPurchase: purchaserPort },
      ),
    ).rejects.toSatisfy((e) => isImmutableFieldError(e) && e.field === 'vendorId')

    const [row] = await db.select().from(review).where(eq(review.userId, USER_ID))
    expect(row?.vendorId).toBe(vendorA)
  })

  it('submit-per-purchase-grain — same user+product, two different purchaseIds → two rows', async () => {
    const db = await freshDb()

    await submitReview(
      db,
      { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 4, purchaseId: PURCHASE_ID_1 },
      { verifiedPurchase: purchaserPort },
    )

    await submitReview(
      db,
      { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 5, purchaseId: PURCHASE_ID_2 },
      { verifiedPurchase: purchaserPort },
    )

    expect(await rowCount(db)).toBe(2)
  })

  it('submit-upsert-same-purchase — same purchaseId → upsert updates rating', async () => {
    const db = await freshDb()

    await submitReview(
      db,
      { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 3, purchaseId: PURCHASE_ID_1 },
      { verifiedPurchase: purchaserPort },
    )

    const updated = await submitReview(
      db,
      { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 5, purchaseId: PURCHASE_ID_1 },
      { verifiedPurchase: purchaserPort },
    )

    expect(await rowCount(db)).toBe(1)
    expect(updated.rating).toBe(5)
  })

  it('submit-missing-purchaseid — empty purchaseId throws ReviewValidationError', async () => {
    const db = await freshDb()

    await expect(
      submitReview(
        db,
        { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 4, purchaseId: '' },
        { verifiedPurchase: createFakeVerifiedPurchasePort({ purchased: true }) },
      ),
    ).rejects.toSatisfy((e) => isReviewValidationError(e) && e.field === 'purchaseId')

    expect(await rowCount(db)).toBe(0)
  })

  it('submit-port-unauthorized — port returns false → NotPurchasedError', async () => {
    const db = await freshDb()

    await expect(
      submitReview(
        db,
        { productId: PRODUCT_ID, userId: USER_ID, vendorId: null, rating: 4, purchaseId: PURCHASE_ID_1 },
        { verifiedPurchase: createFakeVerifiedPurchasePort({ purchased: false }) },
      ),
    ).rejects.toSatisfy((e) => isNotPurchasedError(e))

    expect(await rowCount(db)).toBe(0)
  })
})
