import { describe, expect, it } from 'vitest'
import { getProductById } from '@platform-modules/commerce-catalog'
import { getVendorOrders } from './orders.js'
import { createVendorProduct, updateVendorProduct } from './product.js'
import { computeVendorSplits } from './splits.js'
import {
  USER_A,
  USER_B,
  BUYER,
  freshDb,
  seedApprovedVendor,
  seedVendorOrder,
} from './test-helpers.js'

// Regression tests for the Gate-4 adversarial findings. Each asserts the FIXED
// behavior; pre-fix each of these failed (proven via temporary probes).
describe('secaudit regression — Gate-4 findings', () => {
  // Finding A (A6 scope): getVendorOrders is vendor-scoped ONLY. It must NOT carry
  // actor.userId into listOrders — that adds an OR buyer-branch that leaks the
  // caller's own purchases (from OTHER vendors) into their "vendor orders" view.
  it('secaudit-getVendorOrders-excludes-callers-own-buyer-orders', async () => {
    const db = await freshDb()
    const vendorA = await seedApprovedVendor(db, USER_A, 'Shop A')
    const vendorB = await seedApprovedVendor(db, USER_B, 'Shop B')

    // vendorA's own sale (BUYER purchased from vendorA)
    const vendorASale = await seedVendorOrder(db, BUYER, vendorA.id)
    // USER_A ALSO buys from vendorB as a customer — must NOT appear in vendorA orders
    const userAPurchase = await seedVendorOrder(db, USER_A, vendorB.id)

    const page = await getVendorOrders(db, { userId: USER_A })
    const ids = page.items.map((o) => o.id)

    expect(ids).toContain(vendorASale.id)
    expect(ids).not.toContain(userAPurchase.id)
    expect(ids).toHaveLength(1)
  })

  // Finding A (cont.): an admin-who-is-also-a-vendor calling getVendorOrders gets
  // VENDOR-scoped results, not the unscoped admin firehose (isAdmin not carried).
  it('secaudit-getVendorOrders-admin-vendor-stays-vendor-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 aSale = await seedVendorOrder(db, BUYER, vendorA.id)
    const bSale = await seedVendorOrder(db, BUYER, vendorB.id)

    const page = await getVendorOrders(db, { userId: USER_A, isAdmin: true })
    const ids = page.items.map((o) => o.id)

    expect(ids).toContain(aSale.id)
    expect(ids).not.toContain(bSale.id)
  })

  // Finding B (C4 + A5): createVendorProduct strips any client-supplied `id`.
  // Reusing another vendor's product id must NOT collide/overwrite NOR surface a
  // foreign ImmutableFieldError oracle — it mints a fresh product for the caller.
  it('secaudit-createVendorProduct-ignores-client-id-no-oracle', async () => {
    const db = await freshDb()
    await seedApprovedVendor(db, USER_A, 'Shop A')
    const vendorB = await seedApprovedVendor(db, USER_B, 'Shop B')

    const productB = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_B }, { kind: 'physical', slug: 'collide-b', title: 'B' }),
    )

    // vendorA attempts to reuse productB's id — must succeed as a NEW product, not collide.
    const created = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_A }, {
        id: productB.id,
        kind: 'physical',
        slug: 'collide-a',
        title: 'A new product',
      } as any),
    )

    // New product got a server-minted id (NOT the forged one).
    expect(created.id).not.toBe(productB.id)
    // productB untouched.
    const afterB = await getProductById(db, productB.id, { audience: 'admin' })
    expect(afterB?.title).toBe('B')
    expect(afterB?.vendorId).toBe(vendorB.id)
  })

  // Finding B (cont.): updateVendorProduct ignores a foreign vendorId in the patch
  // (NOT part of ProductPatch) — no throw, vendorId unchanged (clean no-op, no oracle).
  it('secaudit-updateVendorProduct-ignores-foreign-vendorId-patch', async () => {
    const db = await freshDb()
    await seedApprovedVendor(db, USER_A, 'Shop A')
    const vendorB = await seedApprovedVendor(db, USER_B, 'Shop B')
    const productA = await db.transaction((tx) =>
      createVendorProduct(tx, { userId: USER_A }, { kind: 'physical', slug: 'mine-a', title: 'Mine' }),
    )

    const updated = await db.transaction((tx) =>
      updateVendorProduct(tx, { userId: USER_A }, productA.id, {
        title: 'Renamed',
        vendorId: vendorB.id,
        kind: 'digital',
      } as any),
    )

    expect(updated.title).toBe('Renamed')
    expect(updated.vendorId).toBe(productA.vendorId) // unchanged — foreign vendorId ignored
    expect(updated.kind).toBe('physical') // kind immutable, ignored
  })

  // Finding C (money integrity): computeVendorSplits rejects an out-of-[0,10000]
  // rate instead of silently producing negative amounts that still sum to total.
  it('secaudit-computeVendorSplits-rejects-out-of-range-rate', () => {
    const splitInput = {
      lines: [{ vendorId: 'v1', lineTotal: 1000n }],
      subtotal: 1000n,
      total: 1000n,
    }
    expect(() =>
      computeVendorSplits({ ...splitInput, rates: new Map([['v1', 20000]]) }),
    ).toThrow(/rate out of range/)
    expect(() =>
      computeVendorSplits({ ...splitInput, rates: new Map([['v1', -100]]) }),
    ).toThrow(/rate out of range/)
  })
})
