import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
  EUR_PER_MILLION_TOKENS,
  PIPELINE_OVERHEAD_MULTIPLIER,
  TOK_PER_LINE,
  estimateConversionTokens,
  grossEurCentsFromTokens,
  applyLicenseCreditsToEstimate,
} from './conversion-pricing.js';

// ── grossEurCentsFromTokens ───────────────────────────────────────────────────

describe('grossEurCentsFromTokens', () => {
  it('returns 0 for 0 tokens', () => {
    assert.equal(grossEurCentsFromTokens(0), 0);
  });

  it('returns correct cents for exactly 1M tokens', () => {
    // 1M tokens at EUR_PER_MILLION_TOKENS €/M = EUR_PER_MILLION_TOKENS * 100 cents
    assert.equal(grossEurCentsFromTokens(1_000_000), EUR_PER_MILLION_TOKENS * 100);
  });

  it('returns correct cents for 500k tokens', () => {
    assert.equal(grossEurCentsFromTokens(500_000), (EUR_PER_MILLION_TOKENS / 2) * 100);
  });

  it('rounds fractional cents', () => {
    // Any result should be an integer
    const result = grossEurCentsFromTokens(333_333);
    assert.equal(result, Math.round(result));
  });
});

// ── estimateConversionTokens ──────────────────────────────────────────────────

describe('estimateConversionTokens', () => {
  describe('when tokensUsed is set', () => {
    it('returns tokensUsed directly', () => {
      assert.equal(estimateConversionTokens({ tokensUsed: 42_000 }), 42_000);
    });

    it('ignores totalLines when tokensUsed > 0', () => {
      assert.equal(estimateConversionTokens({ tokensUsed: 5_000, totalLines: 10_000 }), 5_000);
    });

    it('falls through when tokensUsed is 0', () => {
      // 0 is falsy — should not return 0 from the early-return path
      const result = estimateConversionTokens({ tokensUsed: 0, totalLines: 100, sourceLanguage: 'java' });
      assert.notEqual(result, 0);
    });

    it('falls through when tokensUsed is null', () => {
      const result = estimateConversionTokens({ tokensUsed: null, totalLines: 100 });
      assert.notEqual(result, null);
    });
  });

  describe('when totalLines is set', () => {
    it('uses TOK_PER_LINE tokens-per-line for a modern language (multiplier = 1)', () => {
      assert.equal(
        estimateConversionTokens({ totalLines: 1000, sourceLanguage: 'java' }),
        Math.round(1000 * TOK_PER_LINE * 1),
      );
    });

    it('applies mainframe multiplier (3) for jcl', () => {
      assert.equal(
        estimateConversionTokens({ totalLines: 1000, sourceLanguage: 'jcl' }),
        Math.round(1000 * TOK_PER_LINE * 3),
      );
    });

    it('applies 2.5 multiplier for unknown legacy language (cobol)', () => {
      assert.equal(
        estimateConversionTokens({ totalLines: 1000, sourceLanguage: 'cobol' }),
        Math.round(1000 * TOK_PER_LINE * 2.5),
      );
    });

    it('ignores estimatedLOC when totalLines is present', () => {
      assert.equal(
        estimateConversionTokens({ totalLines: 500, estimatedLOC: 9999 }),
        Math.round(500 * TOK_PER_LINE * 1),
      );
    });
  });

  describe('when only estimatedLOC is set', () => {
    it('uses estimatedLOC as numeric fallback', () => {
      assert.equal(
        estimateConversionTokens({ estimatedLOC: 1000, sourceLanguage: 'java' }),
        Math.round(1000 * TOK_PER_LINE * 1),
      );
    });

    it('parses string estimatedLOC', () => {
      assert.equal(
        estimateConversionTokens({ estimatedLOC: '2000', sourceLanguage: 'java' }),
        Math.round(2000 * TOK_PER_LINE * 1),
      );
    });
  });

  describe('fallback to default', () => {
    it('returns 100_000 when all inputs are absent', () => {
      assert.equal(estimateConversionTokens({}), 100_000);
    });

    it('returns 100_000 when estimatedLOC is 0', () => {
      assert.equal(estimateConversionTokens({ estimatedLOC: 0 }), 100_000);
    });

    it('returns 100_000 when estimatedLOC is an unparseable string', () => {
      assert.equal(estimateConversionTokens({ estimatedLOC: 'NaN' }), 100_000);
    });
  });
});

// ── applyLicenseCreditsToEstimate ─────────────────────────────────────────────

describe('applyLicenseCreditsToEstimate', () => {
  it('returns grossEurCents with no discount when credits are 0', () => {
    const { grossEurCents, creditsTokensApplied, netEurCents } =
      applyLicenseCreditsToEstimate(1_000_000, 0);
    assert.equal(grossEurCents, EUR_PER_MILLION_TOKENS * 100);
    assert.equal(creditsTokensApplied, 0);
    assert.equal(netEurCents, grossEurCents);
  });

  it('applies credits up to the full estimate when credits exceed usage', () => {
    const { creditsTokensApplied, netEurCents } =
      applyLicenseCreditsToEstimate(500_000, 2_000_000);
    assert.equal(creditsTokensApplied, 500_000);
    assert.equal(netEurCents, 0);
  });

  it('applies partial credits when credits < estimate', () => {
    const estimated = 1_000_000;
    const credits = 400_000;
    const { grossEurCents, creditsTokensApplied, netEurCents } =
      applyLicenseCreditsToEstimate(estimated, credits);
    assert.equal(creditsTokensApplied, 400_000);
    assert.equal(netEurCents, grossEurCents - grossEurCentsFromTokens(400_000));
  });

  it('clamps negative credits to 0', () => {
    const { creditsTokensApplied, netEurCents, grossEurCents } =
      applyLicenseCreditsToEstimate(1_000_000, -500);
    assert.equal(creditsTokensApplied, 0);
    assert.equal(netEurCents, grossEurCents);
  });

  it('netEurCents is never negative', () => {
    const { netEurCents } = applyLicenseCreditsToEstimate(100_000, 999_999_999);
    assert.ok(netEurCents >= 0);
  });

  it('returns integer values', () => {
    const { grossEurCents, creditsTokensApplied, netEurCents } =
      applyLicenseCreditsToEstimate(333_333, 111_111);
    assert.equal(grossEurCents, Math.round(grossEurCents));
    assert.equal(netEurCents, Math.round(netEurCents));
    assert.equal(creditsTokensApplied, Math.round(creditsTokensApplied));
  });
});

// ── constants ─────────────────────────────────────────────────────────────────

describe('constants', () => {
  it('EUR_PER_MILLION_TOKENS is positive', () => {
    assert.ok(EUR_PER_MILLION_TOKENS > 0);
  });

  it('PIPELINE_OVERHEAD_MULTIPLIER is > 1 (adds overhead)', () => {
    assert.ok(PIPELINE_OVERHEAD_MULTIPLIER > 1);
  });

  it('PIPELINE_OVERHEAD_MULTIPLIER is 1.65', () => {
    assert.equal(PIPELINE_OVERHEAD_MULTIPLIER, 1.65);
  });
});

// ── estimateConversionTokens — additional edge cases ─────────────────────────

describe('estimateConversionTokens — edge cases', () => {
  it('returns default when totalLines is 0', () => {
    assert.equal(estimateConversionTokens({ totalLines: 0 }), 100_000);
  });

  it('returns default when totalLines is negative', () => {
    assert.equal(estimateConversionTokens({ totalLines: -50 }), 100_000);
  });

  it('uses totalLines even when tokensUsed is defined but 0', () => {
    const result = estimateConversionTokens({ tokensUsed: 0, totalLines: 1000, sourceLanguage: 'java' });
    assert.equal(result, Math.round(1000 * TOK_PER_LINE * 1));
  });

  it('ignores estimatedLOC when totalLines is positive', () => {
    const result = estimateConversionTokens({ totalLines: 200, estimatedLOC: 99999, sourceLanguage: 'java' });
    assert.equal(result, Math.round(200 * TOK_PER_LINE * 1));
  });

  it('applies 2.5 multiplier for rpg (legacy, not mainframe)', () => {
    assert.equal(
      estimateConversionTokens({ totalLines: 1000, sourceLanguage: 'rpg' }),
      Math.round(1000 * TOK_PER_LINE * 2.5),
    );
  });

  it('applies 3 multiplier for assembler (mainframe)', () => {
    assert.equal(
      estimateConversionTokens({ totalLines: 1000, sourceLanguage: 'assembler' }),
      Math.round(1000 * TOK_PER_LINE * 3),
    );
  });

  it('result is always a positive integer', () => {
    const inputs = [
      {},
      { totalLines: 1 },
      { totalLines: 999_999 },
      { tokensUsed: 12_345 },
      { estimatedLOC: '500', sourceLanguage: 'cobol' },
    ];
    for (const input of inputs) {
      const r = estimateConversionTokens(input);
      assert.ok(Number.isInteger(r), `expected integer for ${JSON.stringify(input)}, got ${r}`);
      assert.ok(r > 0, `expected > 0 for ${JSON.stringify(input)}`);
    }
  });
});

// ── grossEurCentsFromTokens — additional edge cases ───────────────────────────

describe('grossEurCentsFromTokens — additional cases', () => {
  it('scales linearly: 2M tokens = 2× 1M tokens', () => {
    assert.equal(grossEurCentsFromTokens(2_000_000), grossEurCentsFromTokens(1_000_000) * 2);
  });

  it('handles very small token counts without going negative', () => {
    assert.ok(grossEurCentsFromTokens(1) >= 0);
  });
});

// ── applyLicenseCreditsToEstimate — additional cases ─────────────────────────

describe('applyLicenseCreditsToEstimate — additional cases', () => {
  it('creditsTokensApplied is always ≤ estimatedTokens', () => {
    for (const credits of [0, 500_000, 1_000_000, 99_999_999]) {
      const { creditsTokensApplied } = applyLicenseCreditsToEstimate(1_000_000, credits);
      assert.ok(
        creditsTokensApplied <= 1_000_000,
        `applied ${creditsTokensApplied} > estimated 1M for credits=${credits}`,
      );
    }
  });

  it('grossEurCents is independent of credits amount', () => {
    const { grossEurCents: a } = applyLicenseCreditsToEstimate(1_000_000, 0);
    const { grossEurCents: b } = applyLicenseCreditsToEstimate(1_000_000, 500_000);
    assert.equal(a, b);
  });

  it('zero estimated tokens always returns zeros', () => {
    const { grossEurCents, creditsTokensApplied, netEurCents } =
      applyLicenseCreditsToEstimate(0, 1_000_000);
    assert.equal(grossEurCents, 0);
    assert.equal(creditsTokensApplied, 0);
    assert.equal(netEurCents, 0);
  });
});
