import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { eq } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { getProductById, product } from '@platform-modules/commerce-catalog'
import { assertOrderIntegrity } from '@platform-modules/commerce-orders'
import {
  VendorAuthorizationError,
  VendorNotFoundError,
  VendorProductNotFoundError,
  VendorStateError,
  isVendorAuthorizationError,
  isVendorNotFoundError,
  isVendorProductNotFoundError,
  isVendorStateError,
} from './errors.js'
import { assertVendorCanSell, assertVendorOwnership } from './authz.js'
import { resolveCommissionRate, setVendorCommissionRate } from './commission.js'
import { getVendorOrders } from './orders.js'
import { createVendorProduct, updateVendorProduct } from './product.js'
import { vendor } from './schema.js'
import { computeVendorSplits } from './splits.js'
import { applyAsVendor, approveVendor, suspendVendor } from './vendor.js'
import {
  ADMIN,
  BUYER,
  USER_A,
  USER_B,
  USER_C,
  freshDb,
  seedApprovedVendor,
  seedVendorOrder,
} from './test-helpers.js'

const FAKE_VENDOR = '88888888-8888-4888-8888-888888888888'
const FAKE_PRODUCT = '99999999-9999-4999-8999-999999999999'

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

const PRODUCT_B = {
  kind: 'physical' as const,
  slug: 'secaudit-widget-b',
  title: 'Widget B',
}

function vendorRowSnapshot(row: typeof vendor.$inferSelect | undefined) {
  if (!row) return null
  return {
    status: row.status,
    commissionBps: row.commissionBps,
    name: row.name,
    ownerUserId: row.ownerUserId,
  }
}

describe('Gate 4 — secaudit-marketplace-* conformance', () => {
  it('secaudit-marketplace-action-branch-authz', async () => {
    const db = await freshDb()
    const vendorA = await seedApprovedVendor(db, USER_A, 'Shop A')
    const vendorB = await seedApprovedVendor(db, USER_B, 'Shop B')
    const pendingC = await db.transaction((tx) =>
      applyAsVendor(tx, { ownerUserId: USER_C, name: 'Shop C' }),
    )

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

    let updateRejected = false
    try {
      await db.transaction((tx) =>
        updateVendorProduct(tx, { userId: USER_B }, productA.id, { title: 'Stolen' }),
      )
    } catch (e) {
      updateRejected = isVendorProductNotFoundError(e)
    }
    expect(updateRejected).toBe(true)
    const productAfterUpdate = await getProductById(db, productA.id, { audience: 'admin' })
    expect(productAfterUpdate?.title).toBe('Widget A')

    let approveRejected = false
    try {
      await db.transaction((tx) => approveVendor(tx, pendingC.id, { userId: USER_A }))
    } catch (e) {
      approveRejected = isVendorAuthorizationError(e)
    }
    expect(approveRejected).toBe(true)
    const [pendingAfterApprove] = await db.select().from(vendor).where(eq(vendor.id, pendingC.id))
    expect(pendingAfterApprove?.status).toBe('pending')

    let suspendRejected = false
    try {
      await db.transaction((tx) => suspendVendor(tx, vendorA.id, { userId: USER_A }))
    } catch (e) {
      suspendRejected = isVendorAuthorizationError(e)
    }
    expect(suspendRejected).toBe(true)
    const [approvedAfterSuspend] = await db.select().from(vendor).where(eq(vendor.id, vendorA.id))
    expect(approvedAfterSuspend?.status).toBe('approved')

    let rateRejected = false
    try {
      await db.transaction((tx) =>
        setVendorCommissionRate(tx, vendorB.id, { userId: USER_B }, 0),
      )
    } catch (e) {
      rateRejected = isVendorAuthorizationError(e)
    }
    expect(rateRejected).toBe(true)
    const [rateAfterReject] = await db.select().from(vendor).where(eq(vendor.id, vendorB.id))
    expect(rateAfterReject?.commissionBps).toBe(vendorB.commissionBps)
  })

  it('secaudit-marketplace-vendor-ownership-recheck', async () => {
    const db = await freshDb()
    const vendorA = await seedApprovedVendor(db, USER_A, 'Shop A')
    await seedApprovedVendor(db, USER_B, 'Shop B')

    await expect(
      assertVendorOwnership(db, { userId: USER_B, vendorId: vendorA.id }, vendorA.id),
    ).rejects.toSatisfy((e) => isVendorNotFoundError(e))

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

    await expect(
      db.transaction((tx) =>
        updateVendorProduct(
          tx,
          { userId: USER_B, vendorId: vendorA.id },
          productA.id,
          { title: 'Forged vendorId' },
        ),
      ),
    ).rejects.toSatisfy((e) => isVendorProductNotFoundError(e))

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

  it('secaudit-marketplace-update-actor-scoped', async () => {
    const db = await freshDb()
    const vendorA = 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_A),
    )

    await expect(
      db.transaction((tx) =>
        updateVendorProduct(tx, { userId: USER_B }, productA.id, { title: 'Cross-vendor' }),
      ),
    ).rejects.toSatisfy((e) => isVendorProductNotFoundError(e))

    const ownerUpdate = await db.transaction((tx) =>
      updateVendorProduct(tx, { userId: USER_A }, productA.id, { title: 'Owner OK' }),
    )
    expect(ownerUpdate.title).toBe('Owner OK')
    expect(ownerUpdate.vendorId).toBe(vendorA.id)
  })

  it('secaudit-marketplace-create-actor-scoped', 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_A, 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('secaudit-marketplace-authz-before-effect', async () => {
    const db = await freshDb()
    const pending = await db.transaction((tx) =>
      applyAsVendor(tx, { ownerUserId: USER_A, name: 'Shop A' }),
    )
    const approved = await seedApprovedVendor(db, USER_B, 'Shop B')

    const [beforePending] = await db.select().from(vendor).where(eq(vendor.id, pending.id))
    const pendingSnap = vendorRowSnapshot(beforePending)

    await expect(
      db.transaction((tx) => approveVendor(tx, pending.id, { userId: USER_A })),
    ).rejects.toBeInstanceOf(VendorAuthorizationError)

    const [afterPending] = await db.select().from(vendor).where(eq(vendor.id, pending.id))
    expect(vendorRowSnapshot(afterPending)).toEqual(pendingSnap)

    const [beforeApproved] = await db.select().from(vendor).where(eq(vendor.id, approved.id))
    const approvedSnap = vendorRowSnapshot(beforeApproved)

    await expect(
      db.transaction((tx) => suspendVendor(tx, approved.id, { userId: USER_B })),
    ).rejects.toBeInstanceOf(VendorAuthorizationError)

    const [afterApproved] = await db.select().from(vendor).where(eq(vendor.id, approved.id))
    expect(vendorRowSnapshot(afterApproved)).toEqual(approvedSnap)

    const [beforeRate] = await db.select().from(vendor).where(eq(vendor.id, approved.id))
    const rateSnap = beforeRate?.commissionBps

    await expect(
      db.transaction((tx) => setVendorCommissionRate(tx, approved.id, { userId: USER_B }, 100)),
    ).rejects.toBeInstanceOf(VendorAuthorizationError)

    const [afterRate] = await db.select().from(vendor).where(eq(vendor.id, approved.id))
    expect(afterRate?.commissionBps).toBe(rateSnap)
  })

  it('secaudit-marketplace-no-cross-vendor-oracle', async () => {
    const db = await freshDb()
    const vendorA = 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_A),
    )

    let crossVendorError: unknown
    try {
      await assertVendorOwnership(db, { userId: USER_B }, vendorA.id)
    } catch (e) {
      crossVendorError = e
    }

    let fakeVendorError: unknown
    try {
      await assertVendorOwnership(db, { userId: USER_B }, FAKE_VENDOR)
    } catch (e) {
      fakeVendorError = e
    }

    expect(isVendorNotFoundError(crossVendorError)).toBe(true)
    expect(isVendorNotFoundError(fakeVendorError)).toBe(true)
    expect(crossVendorError).toMatchObject({
      name: (fakeVendorError as VendorNotFoundError).name,
      code: (fakeVendorError as VendorNotFoundError).code,
      httpStatus: (fakeVendorError as VendorNotFoundError).httpStatus,
    })

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

    let fakeProductError: unknown
    try {
      await db.transaction((tx) =>
        updateVendorProduct(tx, { userId: USER_B }, FAKE_PRODUCT, { title: 'Oracle' }),
      )
    } catch (e) {
      fakeProductError = e
    }

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

  it('secaudit-marketplace-reads-rederived', async () => {
    const db = await freshDb()
    const vendorA = await seedApprovedVendor(db, USER_A, 'Shop A')
    const vendorB = await seedApprovedVendor(db, USER_B, 'Shop B')

    const orderA = await seedVendorOrder(db, BUYER, vendorA.id)
    await seedVendorOrder(db, BUYER, vendorB.id)

    const page = await getVendorOrders(db, { userId: USER_A, vendorId: vendorB.id })

    expect(page.items).toHaveLength(1)
    expect(page.items[0]?.id).toBe(orderA.id)
    expect(
      page.items.every((o) => o.lines.some((line) => line.vendorId === vendorA.id)),
    ).toBe(true)
    expect(
      page.items.some((o) => o.lines.some((line) => line.vendorId === vendorB.id)),
    ).toBe(false)
  })

  it('secaudit-marketplace-suspended-cannot-sell', async () => {
    const db = await freshDb()
    const pending = await db.transaction((tx) =>
      applyAsVendor(tx, { ownerUserId: USER_A, name: 'Shop A' }),
    )

    await expect(assertVendorCanSell(db, pending.id)).rejects.toSatisfy((e) =>
      isVendorStateError(e),
    )
    await expect(assertVendorCanSell(db, pending.id)).rejects.toBeInstanceOf(VendorStateError)

    const approved = await seedApprovedVendor(db, USER_B, 'Shop B')
    const productB = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_B }, PRODUCT_B),
    )

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

    await expect(assertVendorCanSell(db, approved.id)).rejects.toSatisfy((e) =>
      isVendorStateError(e),
    )

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

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

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

  it('secaudit-marketplace-commission-integrity', async () => {
    const db = await freshDb()
    const vendorA = await seedApprovedVendor(db, USER_A, 'Shop A')
    const beforeRate = await resolveCommissionRate(db, vendorA.id)

    const forcedApply = {
      ownerUserId: USER_C,
      name: 'Shop C',
      commissionBps: 0,
    } as Parameters<typeof applyAsVendor>[1] & { commissionBps: number }
    const createdC = await db.transaction((tx) => applyAsVendor(tx, forcedApply))
    expect(createdC.commissionBps).not.toBe(0)
    expect(createdC.commissionBps).toBe(beforeRate)

    await expect(
      db.transaction((tx) => setVendorCommissionRate(tx, vendorA.id, { userId: USER_A }, 0)),
    ).rejects.toBeInstanceOf(VendorAuthorizationError)

    const [unchanged] = await db.select().from(vendor).where(eq(vendor.id, vendorA.id))
    expect(unchanged?.commissionBps).toBe(beforeRate)

    const subtotal = 10000n
    const total = 11000n
    const splits = computeVendorSplits({
      lines: [{ vendorId: vendorA.id, lineTotal: subtotal }],
      subtotal,
      total,
      rates: new Map([[vendorA.id, beforeRate]]),
    })

    let splitSum = 0n
    for (const split of splits) {
      splitSum += split.amount
    }
    expect(splitSum).toBe(total)

    await db.transaction((tx) => setVendorCommissionRate(tx, vendorA.id, ADMIN, 500))

    const [afterAdminSet] = await db.select().from(vendor).where(eq(vendor.id, vendorA.id))
    expect(afterAdminSet?.commissionBps).toBe(500)

    await expect(
      db.transaction((tx) => setVendorCommissionRate(tx, vendorA.id, { userId: USER_A }, 0)),
    ).rejects.toBeInstanceOf(VendorAuthorizationError)

    const [stillAdminRate] = await db.select().from(vendor).where(eq(vendor.id, vendorA.id))
    expect(stillAdminRate?.commissionBps).toBe(500)
  })

  it('secaudit-marketplace-money-format', async () => {
    const splitsPath = fileURLToPath(new URL('./splits.ts', import.meta.url))
    const source = readFileSync(splitsPath, 'utf8')

    expect(source).not.toMatch(/\/\s*100\b/)
    expect(source).not.toMatch(/\/\s*10000\b/)
    expect(source).not.toMatch(/parseFloat/)
    expect(source).not.toMatch(/\d+\.\d+/)

    const vendorSplits = computeVendorSplits({
      lines: [
        { vendorId: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', lineTotal: 5000n },
        { vendorId: 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', lineTotal: 5000n },
      ],
      subtotal: 10000n,
      total: 11000n,
      rates: new Map([
        ['aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 1000],
        ['bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 1500],
      ]),
    })

    for (const split of vendorSplits) {
      expect(typeof split.amount).toBe('bigint')
    }

    let sum = 0n
    for (const split of vendorSplits) {
      sum += split.amount
    }
    expect(sum).toBe(11000n)
  })
})
