// @vitest-environment node
/**
 * Accrual integration oracle — ledger core + affiliate_entries + accrueVesting (insert-first).
 */
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
import { eq } from 'drizzle-orm'
import { ledgerEntries } from '@platform-modules/ledger'
import { getVesting, ledgerEntryVesting, walletVesting } from '@platform-modules/ledger/vesting'
import { makeTestDb, closeTestDb, resetTables, type TestDb } from './test/pglite-db.js'
import { createUser } from './test/fixtures.js'
import { affiliateEntriesTable } from './schema.js'
import { isInvalidSourceIdError } from './accrual.js'

let testDb: TestDb

const getAccrual = () => import('./accrual.js')

describe('accrueCommission integration', () => {
  beforeAll(async () => {
    testDb = await makeTestDb()
  })

  afterAll(async () => {
    await closeTestDb()
  })

  beforeEach(async () => {
    await resetTables(testDb)
  })

  it('happy path — inserts ledger row, affiliate side-row, credits pending bucket, state=pending', async () => {
    const { accrueCommission } = await getAccrual()
    const { id: userId } = await createUser(testDb)
    const matureAt = new Date(Date.now() + 30 * 86_400_000)
    const idempotencyKey = 'affiliate_commission:purchase:src-001'

    const result = await testDb.transaction((tx) =>
      accrueCommission(tx, {
        userId,
        amountMinor: 1000n,
        entryType: 'affiliate_commission',
        sourceType: 'purchase',
        sourceId: 'src-001',
        matureAt,
      }),
    )

    expect(result.inserted).toBe(true)
    expect(result.amountMinor).toBe(1000n)
    expect(result.state).toBe('pending')

    const ledgerRows = await testDb
      .select()
      .from(ledgerEntries)
      .where(eq(ledgerEntries.idempotencyKey, idempotencyKey))
    expect(ledgerRows).toHaveLength(1)
    expect(ledgerRows[0]!.delta).toBe(1000n)

    const sideRows = await testDb
      .select()
      .from(affiliateEntriesTable)
      .where(eq(affiliateEntriesTable.entryId, ledgerRows[0]!.id))
    expect(sideRows).toHaveLength(1)
    expect(sideRows[0]!.entryType).toBe('affiliate_commission')
    expect(sideRows[0]!.sourceType).toBe('purchase')
    expect(sideRows[0]!.sourceId).toBe('src-001')
    expect(sideRows[0]!.ownerId).toBe(userId)

    const vesting = await getVesting(testDb, userId)
    expect(vesting.pendingMinor).toBe(1000n)
    expect(vesting.maturedMinor).toBe(0n)
    expect(vesting.lifetimeEarnedMinor).toBe(1000n)
  })

  it('idempotency — repeated accrue for same key does not double-credit', async () => {
    const { accrueCommission } = await getAccrual()
    const { id: userId } = await createUser(testDb)
    const matureAt = new Date(Date.now() + 30 * 86_400_000)
    const idempotencyKey = 'affiliate_commission:purchase:src-idem-001'

    const input = {
      userId,
      amountMinor: 500n,
      entryType: 'affiliate_commission' as const,
      sourceType: 'purchase',
      sourceId: 'src-idem-001',
      matureAt,
    }

    const first = await testDb.transaction((tx) => accrueCommission(tx, input))
    const second = await testDb.transaction((tx) => accrueCommission(tx, input))

    expect(first.inserted).toBe(true)
    expect(second.inserted).toBe(false)
    expect(second.amountMinor).toBe(500n)

    const ledgerRows = await testDb
      .select()
      .from(ledgerEntries)
      .where(eq(ledgerEntries.idempotencyKey, idempotencyKey))
    expect(ledgerRows).toHaveLength(1)

    const sideRows = await testDb.select().from(affiliateEntriesTable)
    expect(sideRows).toHaveLength(1)

    const vesting = await getVesting(testDb, userId)
    expect(vesting.pendingMinor).toBe(500n)
  })

  it('creates wallet_vesting row when missing — lifetimeEarned equals inserted amount', async () => {
    const { accrueCommission } = await getAccrual()
    const { id: userId } = await createUser(testDb)

    const before = await testDb
      .select()
      .from(walletVesting)
      .where(eq(walletVesting.ownerId, userId))
    expect(before).toHaveLength(0)

    await testDb.transaction((tx) =>
      accrueCommission(tx, {
        userId,
        amountMinor: 2000n,
        entryType: 'referral_reward',
        sourceType: 'referral',
        sourceId: 'src-wallet-001',
        matureAt: new Date(Date.now() + 30 * 86_400_000),
      }),
    )

    const vesting = await getVesting(testDb, userId)
    expect(vesting.pendingMinor).toBe(2000n)
    expect(vesting.lifetimeEarnedMinor).toBe(2000n)
  })

  it('instant-mature entry credits matured bucket when matureAt <= now', async () => {
    const { accrueCommission } = await getAccrual()
    const { id: userId } = await createUser(testDb)

    const result = await testDb.transaction((tx) =>
      accrueCommission(tx, {
        userId,
        amountMinor: 750n,
        entryType: 'affiliate_commission',
        sourceType: 'purchase',
        sourceId: 'src-instant',
        matureAt: new Date(Date.now() - 1000),
      }),
    )

    expect(result.state).toBe('matured')

    const vesting = await getVesting(testDb, userId)
    expect(vesting.maturedMinor).toBe(750n)
    expect(vesting.pendingMinor).toBe(0n)
  })

  it('zero-amount audit row inserts ledger + side-row without vesting mutation', async () => {
    const { accrueCommission } = await getAccrual()
    const { id: userId } = await createUser(testDb)

    await testDb.transaction((tx) =>
      accrueCommission(tx, {
        userId,
        amountMinor: 1000n,
        entryType: 'affiliate_commission',
        sourceType: 'purchase',
        sourceId: 'src-seed',
        matureAt: new Date(Date.now() + 30 * 86_400_000),
      }),
    )

    const before = await getVesting(testDb, userId)

    const result = await testDb.transaction((tx) =>
      accrueCommission(tx, {
        userId,
        amountMinor: 0n,
        entryType: 'affiliate_commission',
        sourceType: 'purchase',
        sourceId: 'src-audit-zero',
        matureAt: new Date(),
        memo: 'fraud:block',
      }),
    )

    expect(result.inserted).toBe(true)
    expect(result.state).toBe('audit')

    const after = await getVesting(testDb, userId)
    expect(after.pendingMinor).toBe(before.pendingMinor)
    expect(after.maturedMinor).toBe(before.maturedMinor)
    expect(after.lifetimeEarnedMinor).toBe(before.lifetimeEarnedMinor)

    const sideRows = await testDb
      .select()
      .from(affiliateEntriesTable)
      .where(eq(affiliateEntriesTable.sourceId, 'src-audit-zero'))
    expect(sideRows).toHaveLength(1)

    const vestingRows = await testDb
      .select()
      .from(ledgerEntryVesting)
      .where(eq(ledgerEntryVesting.entryId, sideRows[0]!.entryId))
    expect(vestingRows).toHaveLength(0)
  })

  it('I0 precondition — rejects sourceId containing colon (colon-free opaque ids)', async () => {
    const { accrueCommission } = await getAccrual()
    const { id: userId } = await createUser(testDb)

    await expect(
      testDb.transaction((tx) =>
        accrueCommission(tx, {
          userId,
          amountMinor: 100n,
          entryType: 'affiliate_commission',
          sourceType: 'purchase',
          sourceId: 'purchase:uuid-with-colon',
          matureAt: new Date(Date.now() + 86_400_000),
        }),
      ),
    ).rejects.toSatisfy((e: unknown) => isInvalidSourceIdError(e))

    const ledgerRows = await testDb.select().from(ledgerEntries)
    expect(ledgerRows).toHaveLength(0)
  })
})

describe('appendAffiliateLedgerEntry integration', () => {
  beforeAll(async () => {
    testDb = await makeTestDb()
  })

  afterAll(async () => {
    await closeTestDb()
  })

  beforeEach(async () => {
    await resetTables(testDb)
  })

  it('earn via appendAffiliateLedgerEntry — one ledger row + affiliate side-row keyed on entryId', async () => {
    const { appendAffiliateLedgerEntry } = await getAccrual()
    const { id: userId } = await createUser(testDb)
    const matureAt = new Date(Date.now() + 14 * 86_400_000)
    const idempotencyKey = 'referral_reward:referral:ref-earn-001'

    const inserted = await testDb.transaction((tx) =>
      appendAffiliateLedgerEntry(tx, {
        userId,
        amountMinor: 300n,
        entryType: 'referral_reward',
        sourceType: 'referral',
        sourceId: 'ref-earn-001',
        matureAt,
        referralId: undefined,
        resolvedPct: 10,
        memo: 'reward',
      }),
    )

    expect(inserted).toBe(true)

    const [ledgerRow] = await testDb
      .select()
      .from(ledgerEntries)
      .where(eq(ledgerEntries.idempotencyKey, idempotencyKey))
    expect(ledgerRow).toBeDefined()

    const [sideRow] = await testDb
      .select()
      .from(affiliateEntriesTable)
      .where(eq(affiliateEntriesTable.entryId, ledgerRow!.id))
    expect(sideRow!.entryType).toBe('referral_reward')
    expect(sideRow!.sourceType).toBe('referral')
    expect(sideRow!.sourceId).toBe('ref-earn-001')
    expect(sideRow!.resolvedPct).toBe(10)
    expect(sideRow!.memo).toBe('reward')
  })
})
