import { count, eq } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { getProductById, product } from '@platform-modules/commerce-catalog'
import {
  VendorProductNotFoundError,
  VendorStateError,
  isVendorProductNotFoundError,
  isVendorStateError,
} from './errors.js'
import { createVendorProduct, updateVendorProduct } from './product.js'
import { applyAsVendor, approveVendor, suspendVendor } from './vendor.js'
import { ADMIN, USER_A, USER_B, freshDb, seedApprovedVendor } from './test-helpers.js'

const PRODUCT_INPUT = {
  kind: 'physical' as const,
  slug: 'widget-a',
  title: 'Widget A',
}

describe('createVendorProduct', () => {
  it('create-product-derives-vendorId', async () => {
    const db = await freshDb()
    const vendorA = await seedApprovedVendor(db, USER_A, 'Shop A')
    const vendorB = await seedApprovedVendor(db, USER_B, 'Shop B')

    const created = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_A }, {
        ...PRODUCT_INPUT,
        vendorId: vendorB.id,
      }),
    )

    expect(created.vendorId).toBe(vendorA.id)
    expect(created.vendorId).not.toBe(vendorB.id)

    const [row] = await db.select().from(product).where(eq(product.id, created.id))
    expect(row?.vendorId).toBe(vendorA.id)
  })

  it('create-product-suspended-blocked', async () => {
    const db = await freshDb()
    const pending = await db.transaction((tx) =>
      applyAsVendor(tx, { ownerUserId: USER_A, name: 'Shop A' }),
    )

    await expect(
      db.transaction((tx) =>
        createVendorProduct(tx, { userId: USER_A }, PRODUCT_INPUT),
      ),
    ).rejects.toSatisfy((e) => isVendorStateError(e))

    const rows = await db
      .select({ count: count() })
      .from(product)
      .where(eq(product.vendorId, pending.id))
    expect(rows[0]?.count ?? 0).toBe(0)

    await db.transaction((tx) => suspendVendor(tx, pending.id, ADMIN))

    const suspended = await db.transaction((tx) =>
      applyAsVendor(tx, { ownerUserId: USER_B, name: 'Shop B' }),
    )
    await db.transaction((tx) => approveVendor(tx, suspended.id, ADMIN))
    await db.transaction((tx) => suspendVendor(tx, suspended.id, ADMIN))

    await expect(
      db.transaction((tx) =>
        createVendorProduct(tx, { userId: USER_B }, { ...PRODUCT_INPUT, slug: 'widget-b' }),
      ),
    ).rejects.toSatisfy((e) => isVendorStateError(e))
  })
})

describe('updateVendorProduct', () => {
  it('update-product-owner', async () => {
    const db = await freshDb()
    await seedApprovedVendor(db, USER_A, 'Shop A')

    const created = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_A }, PRODUCT_INPUT),
    )

    const updated = await db.transaction((tx) =>
      updateVendorProduct(tx, { userId: USER_A }, created.id, {
        title: 'Widget A Updated',
        status: 'active',
      }),
    )

    expect(updated.title).toBe('Widget A Updated')
    expect(updated.status).toBe('active')
    expect(updated.vendorId).toBe(created.vendorId)
    expect(updated.kind).toBe(created.kind)
  })

  it('update-product-cross-vendor-404', async () => {
    const db = await freshDb()
    await seedApprovedVendor(db, USER_A, 'Shop A')
    await seedApprovedVendor(db, USER_B, 'Shop B')

    const productA = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_A }, PRODUCT_INPUT),
    )

    const fakeId = '88888888-8888-4888-8888-888888888888'

    let crossError: unknown
    try {
      await db.transaction((tx) =>
        updateVendorProduct(tx, { userId: USER_B }, productA.id, { title: 'Stolen' }),
      )
    } catch (e) {
      crossError = e
    }

    let fakeError: unknown
    try {
      await db.transaction((tx) =>
        updateVendorProduct(tx, { userId: USER_B }, fakeId, { title: 'Stolen' }),
      )
    } catch (e) {
      fakeError = e
    }

    expect(isVendorProductNotFoundError(crossError)).toBe(true)
    expect(isVendorProductNotFoundError(fakeError)).toBe(true)
    expect(crossError).toMatchObject({
      name: (fakeError as VendorProductNotFoundError).name,
      code: (fakeError as VendorProductNotFoundError).code,
      httpStatus: (fakeError as VendorProductNotFoundError).httpStatus,
    })

    const unchanged = await getProductById(db, productA.id, { audience: 'admin' })
    expect(unchanged?.title).toBe('Widget A')
  })

  it('update-product-suspended-blocked', async () => {
    const db = await freshDb()
    const vendorRow = await seedApprovedVendor(db, USER_A, 'Shop A')
    const created = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_A }, PRODUCT_INPUT),
    )

    await db.transaction((tx) => suspendVendor(tx, vendorRow.id, ADMIN))

    await expect(
      db.transaction((tx) =>
        updateVendorProduct(tx, { userId: USER_A }, created.id, { title: 'Blocked' }),
      ),
    ).rejects.toSatisfy((e) => isVendorStateError(e))

    const unchanged = await getProductById(db, created.id, { audience: 'admin' })
    expect(unchanged?.title).toBe('Widget A')

    const adminUpdated = await db.transaction((tx) =>
      updateVendorProduct(tx, ADMIN, created.id, { title: 'Admin OK' }),
    )
    expect(adminUpdated.title).toBe('Admin OK')
  })

  it('update-product-vendorId-immutable', async () => {
    const db = await freshDb()
    const vendorA = await seedApprovedVendor(db, USER_A, 'Shop A')
    const vendorB = await seedApprovedVendor(db, USER_B, 'Shop B')

    const created = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_A }, PRODUCT_INPUT),
    )

    // vendorId is NOT part of ProductPatch — a forged value is IGNORED (taken from
    // the loaded row), a clean no-op, NOT a foreign ImmutableFieldError oracle.
    const forcedPatch = { vendorId: vendorB.id } as Parameters<typeof updateVendorProduct>[3] & {
      vendorId: string
    }

    const updated = await db.transaction((tx) =>
      updateVendorProduct(tx, { userId: USER_A }, created.id, forcedPatch),
    )
    expect(updated.vendorId).toBe(vendorA.id)

    const still = await getProductById(db, created.id, { audience: 'admin' })
    expect(still?.vendorId).toBe(vendorA.id)
  })
})
