/**
 * Unit tests for computeMatureAt.
 * I0 guard type surface for colon-free earn sourceIds.
 */
// @vitest-environment node
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
import { computeMatureAt, InvalidSourceIdError, isInvalidSourceIdError, isUnsupportedAccrualEntryTypeError } from './accrual.js'
import { makeTestDb, closeTestDb, resetTables, type TestDb } from './test/pglite-db.js'
import { createUser } from './test/fixtures.js'

const DAY_MS = 86_400_000

const paidAt = new Date('2026-01-01T00:00:00Z')
const expiresAt = new Date('2026-02-01T00:00:00Z')
const beforePaid = new Date('2025-12-01T00:00:00Z')
const redeemedAt = new Date('2026-01-15T00:00:00Z')

describe('computeMatureAt — coupon + redeemed', () => {
  it('coupon + redeemedAt: anchor = redeemedAt + holdDays', () => {
    const result = computeMatureAt(
      { kind: 'coupon', paidAt, expiresAt, redeemedAt },
      { holdDays: 14 },
    )
    expect(result.getTime()).toBe(redeemedAt.getTime() + 14 * DAY_MS)
  })

  it('coupon + redeemedAt + holdDays=0: matureAt == redeemedAt', () => {
    const result = computeMatureAt(
      { kind: 'coupon', paidAt, expiresAt, redeemedAt },
      { holdDays: 0 },
    )
    expect(result.getTime()).toBe(redeemedAt.getTime())
  })
})

describe('computeMatureAt — coupon + not redeemed', () => {
  it('coupon + not redeemed + expiresAt after paidAt → expiresAt + holdDays', () => {
    const result = computeMatureAt(
      { kind: 'coupon', paidAt, expiresAt, redeemedAt: null },
      { holdDays: 30 },
    )
    expect(result.getTime()).toBe(expiresAt.getTime() + 30 * DAY_MS)
  })

  it('coupon + not redeemed + expiresAt before paidAt → paidAt + holdDays', () => {
    const result = computeMatureAt(
      { kind: 'coupon', paidAt, expiresAt: beforePaid, redeemedAt: null },
      { holdDays: 30 },
    )
    expect(result.getTime()).toBe(paidAt.getTime() + 30 * DAY_MS)
  })

  it('coupon + not redeemed + no expiresAt → paidAt + holdDays', () => {
    const result = computeMatureAt(
      { kind: 'coupon', paidAt, expiresAt: null, redeemedAt: null },
      { holdDays: 14 },
    )
    expect(result.getTime()).toBe(paidAt.getTime() + 14 * DAY_MS)
  })
})

describe('computeMatureAt — physical', () => {
  it('physical → paidAt + holdDays', () => {
    const result = computeMatureAt(
      { kind: 'physical', paidAt, expiresAt: null, redeemedAt: null },
      { holdDays: 30 },
    )
    expect(result.getTime()).toBe(paidAt.getTime() + 30 * DAY_MS)
  })

  it('physical + holdDays=0 → matureAt == paidAt', () => {
    const result = computeMatureAt(
      { kind: 'physical', paidAt, expiresAt: null, redeemedAt: null },
      { holdDays: 0 },
    )
    expect(result.getTime()).toBe(paidAt.getTime())
  })
})

describe('InvalidSourceIdError (I0 — colon-free earn sourceIds)', () => {
  it('isInvalidSourceIdError identifies the typed affiliate error', () => {
    const e = new InvalidSourceIdError('bad:id')
    expect(isInvalidSourceIdError(e)).toBe(true)
    expect(isInvalidSourceIdError(new Error('x'))).toBe(false)
    expect(e.code).toBe('INVALID_SOURCE_ID')
  })
})

describe('accrueCommission — positive non-earn state domain (2b)', () => {
  let testDb: TestDb

  beforeAll(async () => {
    testDb = await makeTestDb()
  })

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

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

  it('positive redemption rejects at boundary — does not report matured', async () => {
    const { accrueCommission } = await import('./accrual.js')
    const { id: userId } = await createUser(testDb)

    await expect(
      testDb.transaction((tx) =>
        accrueCommission(tx, {
          userId,
          amountMinor: 100n,
          entryType: 'redemption',
          sourceType: 'affiliate_payout',
          sourceId: 'payout-001',
          matureAt: new Date(),
        }),
      ),
    ).rejects.toSatisfy((e: unknown) => isUnsupportedAccrualEntryTypeError(e))
  })

  it('positive redemption rejects via @internal appendAffiliateLedgerEntry chokepoint', async () => {
    const { appendAffiliateLedgerEntry } = await import('./accrual.js')
    const { id: userId } = await createUser(testDb)

    await expect(
      testDb.transaction((tx) =>
        appendAffiliateLedgerEntry(tx, {
          userId,
          amountMinor: 100n,
          entryType: 'redemption',
          sourceType: 'affiliate_payout',
          sourceId: 'payout-001',
          matureAt: new Date(),
        }),
      ),
    ).rejects.toSatisfy((e: unknown) => isUnsupportedAccrualEntryTypeError(e))
  })
})
