import {
  bifurcateInclusive,
  applyTaxOnBase,
  extractBase,
  roundToStep,
  type TaxSlabSnapshotInput,
} from './tax-bifurcation.helper';

// ─── Fixtures ──────────────────────────────────────────────────────────────

const VAT_5: TaxSlabSnapshotInput = {
  slab_id: 'slab-vat-5',
  slab_name: 'VAT 5%',
  total_rate: 5,
  components: [{ name: 'VAT', rate: 5 }],
};

const GST_18: TaxSlabSnapshotInput = {
  slab_id: 'slab-gst-18',
  slab_name: 'GST 18%',
  total_rate: 18,
  components: [
    { name: 'CGST', rate: 9 },
    { name: 'SGST', rate: 9 },
  ],
};

const TOURISM_PLUS_VAT: TaxSlabSnapshotInput = {
  slab_id: 'slab-mixed',
  slab_name: 'VAT 5% + Tourism 7%',
  total_rate: 12,
  components: [
    { name: 'VAT', rate: 5 },
    { name: 'Tourism', rate: 7 },
  ],
};

const EXEMPT: TaxSlabSnapshotInput = {
  slab_id: 'slab-exempt',
  slab_name: 'Exempt',
  total_rate: 0,
  components: [],
};

// ─── roundToStep ───────────────────────────────────────────────────────────

describe('roundToStep', () => {
  it('rounds to whole units when step=1', () => {
    expect(roundToStep(95.238, 1)).toBe(95);
    expect(roundToStep(95.5, 1)).toBe(96); // half-up
    expect(roundToStep(95.49, 1)).toBe(95);
  });

  it('rounds to nearest 5 when step=5', () => {
    expect(roundToStep(93, 5)).toBe(95);
    expect(roundToStep(92, 5)).toBe(90);
    expect(roundToStep(847.46, 5)).toBe(845);
  });

  it('treats step≤0 as step=1 (defensive)', () => {
    expect(roundToStep(95.7, 0)).toBe(96);
    expect(roundToStep(95.7, -1)).toBe(96);
  });

  it('handles zero', () => {
    expect(roundToStep(0, 1)).toBe(0);
    expect(roundToStep(0, 5)).toBe(0);
  });
});

// ─── bifurcateInclusive — exempt cases ─────────────────────────────────────

describe('bifurcateInclusive — exempt slabs', () => {
  it('null slab → no tax, base equals total', () => {
    const r = bifurcateInclusive(100, null);
    expect(r.base).toBe(100);
    expect(r.tax_amount).toBe(0);
    expect(r.total).toBe(100);
    expect(r.components).toEqual([]);
    expect(r.snapshot.slab_id).toBeNull();
  });

  it('explicit exempt slab → no tax, snapshot preserves the slab ref', () => {
    const r = bifurcateInclusive(100, EXEMPT);
    expect(r.base).toBe(100);
    expect(r.tax_amount).toBe(0);
    expect(r.snapshot.slab_id).toBe('slab-exempt');
    expect(r.snapshot.slab_name).toBe('Exempt');
  });

  it('total = 0 produces zeros all round', () => {
    const r = bifurcateInclusive(0, VAT_5);
    expect(r.base).toBe(0);
    expect(r.tax_amount).toBe(0);
    expect(r.total).toBe(0);
  });
});

// ─── bifurcateInclusive — single component (VAT 5%) ────────────────────────

describe('bifurcateInclusive — single-component slab', () => {
  it('AED 100 incl @ VAT 5%, rounding=1 → base 95, tax 5', () => {
    const r = bifurcateInclusive(100, VAT_5, 1);
    expect(r.base).toBe(95);
    expect(r.tax_amount).toBe(5);
    expect(r.total).toBe(100);
    expect(r.components).toEqual([{ name: 'VAT', rate: 5, amount: 5 }]);
  });

  it('AED 105 incl @ VAT 5%, rounding=1 → base 100, tax 5', () => {
    const r = bifurcateInclusive(105, VAT_5, 1);
    expect(r.base).toBe(100);
    expect(r.tax_amount).toBe(5);
    expect(r.total).toBe(105);
  });

  it('snapshot captures the slab structure by value', () => {
    const r = bifurcateInclusive(100, VAT_5, 1);
    expect(r.snapshot.slab_id).toBe('slab-vat-5');
    expect(r.snapshot.slab_name).toBe('VAT 5%');
    expect(r.snapshot.total_rate).toBe(5);
    expect(r.snapshot.components).toEqual([{ name: 'VAT', rate: 5, amount: 5 }]);
  });
});

// ─── bifurcateInclusive — multi-component (GST 18%) ────────────────────────

describe('bifurcateInclusive — multi-component slab', () => {
  it('INR 118 incl @ GST 18% (9+9), rounding=1 → base 100, CGST 9 + SGST 9', () => {
    const r = bifurcateInclusive(118, GST_18, 1);
    expect(r.base).toBe(100);
    expect(r.tax_amount).toBe(18);
    expect(r.components).toEqual([
      { name: 'CGST', rate: 9, amount: 9 },
      { name: 'SGST', rate: 9, amount: 9 },
    ]);
  });

  it('component sum ALWAYS equals tax_amount (drift absorbed by last component)', () => {
    // Pick a total that produces a non-clean split so the last component
    // catches the drift. INR 1000 @ 18% rounded to nearest 1 → tax 153,
    // CGST = round(153 × 9/18) = round(76.5) = 77, SGST = 153-77 = 76.
    const r = bifurcateInclusive(1000, GST_18, 1);
    const componentsSum = r.components.reduce((s, c) => s + c.amount, 0);
    expect(componentsSum).toBe(r.tax_amount);
  });

  it('mixed components (VAT 5% + Tourism 7%) split proportionally', () => {
    // AED 112 incl @ 12% → base 100, tax 12. VAT = round(12×5/12) = 5,
    // Tourism = 12-5 = 7.
    const r = bifurcateInclusive(112, TOURISM_PLUS_VAT, 1);
    expect(r.base).toBe(100);
    expect(r.tax_amount).toBe(12);
    expect(r.components).toEqual([
      { name: 'VAT', rate: 5, amount: 5 },
      { name: 'Tourism', rate: 7, amount: 7 },
    ]);
  });
});

// ─── bifurcateInclusive — alternate rounding step ──────────────────────────

describe('bifurcateInclusive — rounding step ≠ 1', () => {
  it('INR 1000 incl @ GST 18%, rounding=5 → base/tax both rounded to 5s', () => {
    // raw base = 847.457... → round to nearest 5 = 845, tax = 1000-845 = 155
    const r = bifurcateInclusive(1000, GST_18, 5);
    expect(r.base).toBe(845);
    expect(r.tax_amount).toBe(155);
    expect(r.total).toBe(1000);
    // CGST raw = 155 × 9/18 = 77.5 → round to 5 → 80 (half-up), SGST = 155-80 = 75
    expect(r.components[0].amount).toBe(80);
    expect(r.components[1].amount).toBe(75);
    const sum = r.components.reduce((s, c) => s + c.amount, 0);
    expect(sum).toBe(r.tax_amount);
  });
});

// ─── applyTaxOnBase ────────────────────────────────────────────────────────

describe('applyTaxOnBase', () => {
  it('base 100 @ VAT 5% → tax 5, total 105', () => {
    const r = applyTaxOnBase(100, VAT_5, 1);
    expect(r.base).toBe(100);
    expect(r.tax_amount).toBe(5);
    expect(r.total).toBe(105);
    expect(r.components).toEqual([{ name: 'VAT', rate: 5, amount: 5 }]);
  });

  it('base 100 @ GST 18% → tax 18 split 9 / 9', () => {
    const r = applyTaxOnBase(100, GST_18, 1);
    expect(r.tax_amount).toBe(18);
    expect(r.total).toBe(118);
    expect(r.components).toEqual([
      { name: 'CGST', rate: 9, amount: 9 },
      { name: 'SGST', rate: 9, amount: 9 },
    ]);
  });

  it('null slab → tax 0, total = base', () => {
    const r = applyTaxOnBase(100, null);
    expect(r.tax_amount).toBe(0);
    expect(r.total).toBe(100);
  });

  it('round-trip: bifurcate(applyTaxOnBase(b).total) recovers base (within rounding)', () => {
    const forward = applyTaxOnBase(100, GST_18, 1);
    const back = bifurcateInclusive(forward.total, GST_18, 1);
    expect(back.base).toBe(100);
  });
});

// ─── extractBase ───────────────────────────────────────────────────────────

describe('extractBase', () => {
  it('matches bifurcateInclusive.base for the common case', () => {
    expect(extractBase(118, GST_18, 1)).toBe(100);
    expect(extractBase(105, VAT_5, 1)).toBe(100);
    expect(extractBase(112, TOURISM_PLUS_VAT, 1)).toBe(100);
  });

  it('null/exempt slab → returns the total unchanged', () => {
    expect(extractBase(100, null)).toBe(100);
    expect(extractBase(100, EXEMPT)).toBe(100);
  });

  it('honours the rounding step', () => {
    // Same numbers as bifurcateInclusive test above with step=5
    expect(extractBase(1000, GST_18, 5)).toBe(845);
  });
});

// ─── Defensive edge cases ──────────────────────────────────────────────────

describe('bifurcateInclusive — defensive edges', () => {
  it('slab with total_rate>0 but empty components list → single anonymous tax row', () => {
    const weird: TaxSlabSnapshotInput = {
      slab_id: 'weird',
      slab_name: 'Flat 10%',
      total_rate: 10,
      components: [],
    };
    const r = bifurcateInclusive(110, weird, 1);
    expect(r.base).toBe(100);
    expect(r.tax_amount).toBe(10);
    expect(r.components).toEqual([{ name: 'Flat 10%', rate: 10, amount: 10 }]);
  });

  it('slab with negative total_rate is treated as exempt (no crash)', () => {
    const bad: TaxSlabSnapshotInput = {
      slab_id: 'bad',
      slab_name: 'Bad',
      total_rate: -5,
      components: [],
    };
    const r = bifurcateInclusive(100, bad, 1);
    expect(r.tax_amount).toBe(0);
    expect(r.base).toBe(100);
  });
});
