import { describe, expect, it } from 'vitest'
import { fromBasisPoints, fromPercent } from './resolve.js'
import { InvalidAmountError, applyVat, extractVat } from './vat-math.js'

/** Exclusive VAT reference: `ROUND(net * rate, 2)` with rate as decimal fraction in agorot. */
function exclusiveVat(netAgorot: bigint, rateFraction: string): bigint {
  const [whole, frac = ''] = rateFraction.split('.')
  const padded = (frac + '0000').slice(0, 4)
  const rateBp = BigInt(whole || '0') * 10_000n + BigInt(padded || '0')
  const product = netAgorot * rateBp
  return (2n * product + 10_000n) / (2n * 10_000n)
}

/** Inclusive VAT reference: `Math.round(gross * pct / (100 + pct))` in agorot. */
function inclusiveVat(grossAgorot: bigint, ratePercent: number): bigint {
  const rateBp = BigInt(ratePercent) * 100n
  const numerator = grossAgorot * rateBp
  const denominator = 10_000n + rateBp
  return (2n * numerator + denominator) / (2n * denominator)
}

describe('applyVat (exclusive)', () => {
  it('matches hand-computed agorot for known net amounts', () => {
    const net = 10_000n
    const rate = fromBasisPoints(1700n)
    const { vat, gross } = applyVat(net, rate)
    expect(vat).toBe(exclusiveVat(net, '0.17'))
    expect(gross).toBe(net + vat)
    expect(vat).toBe(1700n)
    expect(gross).toBe(11_700n)
  })

  it('rounds half-up at the exact-half boundary (50 agorot @ 17% → vat 9)', () => {
    const { vat, gross } = applyVat(50n, fromPercent(17))
    expect(vat).toBe(9n)
    expect(gross).toBe(59n)
  })

  it('returns zero VAT at 0% rate', () => {
    const { vat, gross } = applyVat(1234n, fromPercent(0))
    expect(vat).toBe(0n)
    expect(gross).toBe(1234n)
  })

  it('accepts a zero amount (valid 0-VAT line)', () => {
    expect(applyVat(0n, fromPercent(17))).toEqual({ vat: 0n, gross: 0n })
  })

  it('rejects a negative net amount (outside the round-half-up non-negative domain)', () => {
    expect(() => applyVat(-1n, fromPercent(17))).toThrow(InvalidAmountError)
  })
})

describe('extractVat (inclusive)', () => {
  it('matches hand-computed round(gross×pct/(100+pct)) for known gross amounts', () => {
    const gross = 11_700n
    const rate = fromPercent(17)
    const { net, vat } = extractVat(gross, rate)
    expect(vat).toBe(inclusiveVat(gross, 17))
    expect(net).toBe(gross - vat)
    expect(vat).toBe(1700n)
    expect(net).toBe(10_000n)
  })

  it('returns net === gross at 0% rate', () => {
    const { net, vat } = extractVat(999n, fromPercent(0))
    expect(vat).toBe(0n)
    expect(net).toBe(999n)
  })

  it('rejects a negative gross amount (outside the round-half-up non-negative domain)', () => {
    expect(() => extractVat(-1n, fromPercent(17))).toThrow(InvalidAmountError)
  })
})

describe('extractVat ∘ applyVat left-inverse', () => {
  const nets = [1n, 50n, 85n, 86n, 100n, 9_999n, 1_000_000n]

  it.each(nets)('recovers net=%s after exclusive then inclusive', (net) => {
    const rate = fromPercent(17)
    const applied = applyVat(net, rate)
    const extracted = extractVat(applied.gross, rate)
    expect(extracted.net).toBe(net)
  })
})
