import { describe, expect, it } from 'vitest'
import { EU_VAT_SCHEDULES, IL_VAT_SCHEDULE } from './dataset.js'
import {
  InvalidVatDateError,
  InvalidVatRateError,
  NoRateForDateError,
  fractionToBasisPoints,
  fromBasisPoints,
  fromFraction,
  fromPercent,
  normalizeToBasisPoints,
  resolveVatRate,
} from './resolve.js'

describe('resolveVatRate', () => {
  it('picks the 2010-era IL rate (1600 bp), not the current rate', () => {
    const rate = resolveVatRate(IL_VAT_SCHEDULE, '2010-06-15')
    expect(rate).toBe(fromBasisPoints(1600n))
  })

  it('includes the boundary date itself (effectiveFrom day resolves to that entry)', () => {
    const rate = resolveVatRate(IL_VAT_SCHEDULE, '2010-01-01')
    expect(rate).toBe(fromBasisPoints(1600n))
  })

  it('throws NoRateForDateError before the first IL entry', () => {
    expect(() => resolveVatRate(IL_VAT_SCHEDULE, '1976-06-30')).toThrow(NoRateForDateError)
    try {
      resolveVatRate(IL_VAT_SCHEDULE, '1976-06-30')
    } catch (err) {
      expect(err).toBeInstanceOf(NoRateForDateError)
      expect((err as NoRateForDateError).firstEffectiveFrom).toBe('1976-07-01')
    }
  })

  it('throws InvalidVatDateError on a malformed date string (trust boundary)', () => {
    expect(() => resolveVatRate(IL_VAT_SCHEDULE, '2025-13-99')).toThrow(InvalidVatDateError)
    expect(() => resolveVatRate(IL_VAT_SCHEDULE, 'not-a-date')).toThrow(InvalidVatDateError)
    expect(() => resolveVatRate(IL_VAT_SCHEDULE, '')).toThrow(InvalidVatDateError)
    try {
      resolveVatRate(IL_VAT_SCHEDULE, '2025-13-99')
    } catch (err) {
      expect(err).toBeInstanceOf(InvalidVatDateError)
      expect((err as InvalidVatDateError).value).toBe('2025-13-99')
    }
  })

  it('rejects a JS Date at the type level — no Date overload (legal TZ lives in the host)', () => {
    // @ts-expect-error — `date` is a host-resolved YYYY-MM-DD string ONLY; a Date is a type error.
    expect(() => resolveVatRate(IL_VAT_SCHEDULE, new Date('2025-01-01'))).toThrow(InvalidVatDateError)
  })

  it('resolves EU country rows independently of IL', () => {
    const deSchedule = EU_VAT_SCHEDULES.DE!
    const frSchedule = EU_VAT_SCHEDULES.FR!
    const de = resolveVatRate(deSchedule, '2020-01-01')
    const fr = resolveVatRate(frSchedule, '2020-01-01')
    const il = resolveVatRate(IL_VAT_SCHEDULE, '2020-01-01')

    expect(de).toBe(fromBasisPoints(1900n))
    expect(fr).toBe(fromBasisPoints(2000n))
    expect(il).toBe(fromBasisPoints(1700n))
  })
})

describe('VatRate constructors', () => {
  it('fromBasisPoints brands opaque bp without accepting raw numbers at call sites', () => {
    expect(fromBasisPoints(1700)).toBe(fromBasisPoints(1700n))
  })

  it('fromBasisPoints accepts zero (0% reverse-charge rate)', () => {
    expect(fromBasisPoints(0n)).toBe(fromBasisPoints(0))
  })

  it('fromBasisPoints rejects a negative rate (trust boundary — money floor)', () => {
    expect(() => fromBasisPoints(-1n)).toThrow(InvalidVatRateError)
    expect(() => fromBasisPoints(-1700)).toThrow(InvalidVatRateError)
  })

  it('fromBasisPoints rejects a non-integer number', () => {
    expect(() => fromBasisPoints(1700.5)).toThrow(InvalidVatRateError)
  })

  it('fromPercent rejects negative and non-integer percent', () => {
    expect(() => fromPercent(-5)).toThrow(InvalidVatRateError)
    expect(() => fromPercent(17.5)).toThrow(InvalidVatRateError)
  })

  it('fromFraction rejects a negative fraction', () => {
    expect(() => fromFraction('-0.17')).toThrow(InvalidVatRateError)
    expect(() => fromFraction(-0.17)).toThrow(InvalidVatRateError)
  })

  it('fromFraction rejects a malformed fraction string with a typed error (not raw SyntaxError)', () => {
    expect(() => fromFraction('abc')).toThrow(InvalidVatRateError)
    expect(() => fromFraction('')).toThrow(InvalidVatRateError)
    expect(() => fractionToBasisPoints('1.2.3')).toThrow(InvalidVatRateError)
  })

  it('fractionToBasisPoints converts 4-decimal fractions exactly (.165 → 1650)', () => {
    expect(fractionToBasisPoints('0.1650')).toBe(1650n)
    expect(fractionToBasisPoints('0.17')).toBe(1700n)
  })

  it('fractionToBasisPoints rejects sub-basis-point precision instead of silently truncating', () => {
    expect(() => fractionToBasisPoints('0.17005')).toThrow(InvalidVatRateError)
    expect(() => fractionToBasisPoints('0.00005')).toThrow(InvalidVatRateError)
    expect(() => fromFraction(0.170049)).toThrow(InvalidVatRateError)
    expect(fractionToBasisPoints('0.170000')).toBe(1700n)
  })

  it('normalizeToBasisPoints honors the declared unit (never magnitude-infers)', () => {
    expect(normalizeToBasisPoints(17, 'percent')).toBe(1700n)
    expect(normalizeToBasisPoints('0.1700', 'fraction')).toBe(1700n)
    expect(normalizeToBasisPoints(1700, 'basisPoints')).toBe(1700n)
    expect(normalizeToBasisPoints('1700', 'basisPoints')).toBe(1700n)
  })

  it('normalizeToBasisPoints rejects a fractional percent/bp (integer-only units)', () => {
    expect(() => normalizeToBasisPoints(5.5, 'percent')).toThrow(InvalidVatRateError)
    expect(() => normalizeToBasisPoints('1700.5', 'basisPoints')).toThrow(InvalidVatRateError)
  })
})
