import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
  RISK_MULTIPLIERS,
  RISK_LABELS,
  computeFeatureFactors,
  featureMultiplierFromFactors,
} from './cost-approval-factors.js';

// ── RISK_MULTIPLIERS ──────────────────────────────────────────────────────────

describe('RISK_MULTIPLIERS', () => {
  it('low risk applies a 10% discount', () => {
    assert.equal(RISK_MULTIPLIERS['low'], 0.9);
  });

  it('medium risk is the baseline (1.0)', () => {
    assert.equal(RISK_MULTIPLIERS['medium'], 1.0);
  });

  it('high risk adds 20%', () => {
    assert.equal(RISK_MULTIPLIERS['high'], 1.2);
  });

  it('critical risk adds 40%', () => {
    assert.equal(RISK_MULTIPLIERS['critical'], 1.4);
  });
});

// ── RISK_LABELS ───────────────────────────────────────────────────────────────

describe('RISK_LABELS', () => {
  it('has a label for every multiplier key', () => {
    for (const key of Object.keys(RISK_MULTIPLIERS)) {
      assert.ok(key in RISK_LABELS, `missing label for risk level "${key}"`);
    }
  });

  it('low label mentions −10%', () => {
    assert.ok(RISK_LABELS['low']!.includes('−10%') || RISK_LABELS['low']!.includes('-10%'));
  });

  it('high label mentions +20%', () => {
    assert.ok(RISK_LABELS['high']!.includes('+20%'));
  });

  it('critical label mentions +40%', () => {
    assert.ok(RISK_LABELS['critical']!.includes('+40%'));
  });
});

// ── computeFeatureFactors ─────────────────────────────────────────────────────

describe('computeFeatureFactors — always-on factors', () => {
  it('always includes pipeline overhead as the first factor', () => {
    const factors = computeFeatureFactors({});
    assert.ok(factors.length > 0);
    assert.ok(factors[0]!.label.toLowerCase().includes('pipeline'));
    assert.equal(factors[0]!.delta, 0.30);
  });

  it('pipeline overhead delta is 0.30', () => {
    const factors = computeFeatureFactors({});
    const pipeline = factors.find(f => f.label.toLowerCase().includes('pipeline'));
    assert.ok(pipeline, 'pipeline factor not found');
    assert.equal(pipeline.delta, 0.30);
  });
});

describe('computeFeatureFactors — test generation', () => {
  it('adds test generation delta 0.35 when generateTests=true', () => {
    const factors = computeFeatureFactors({ generateTests: true });
    const f = factors.find(f => f.delta === 0.35);
    assert.ok(f, 'expected test generation factor with delta 0.35');
    assert.ok(f.label.toLowerCase().includes('test'));
  });

  it('adds test scaffolding delta 0.18 by default (addTests defaults to true)', () => {
    const factors = computeFeatureFactors({});
    const f = factors.find(f => f.delta === 0.18);
    assert.ok(f, 'expected test scaffolding factor with delta 0.18');
    assert.ok(f.label.toLowerCase().includes('test'));
  });

  it('generateTests=true takes precedence over addTests default — only one test factor', () => {
    const factors = computeFeatureFactors({ generateTests: true });
    const testFactors = factors.filter(f => f.label.toLowerCase().includes('test'));
    assert.equal(testFactors.length, 1);
    assert.equal(testFactors[0]!.delta, 0.35);
  });

  it('omits both test factors when addTests=false and generateTests is not set', () => {
    const factors = computeFeatureFactors({ addTests: false });
    const testFactors = factors.filter(f => f.label.toLowerCase().includes('test'));
    assert.equal(testFactors.length, 0);
  });

  it('omits test scaffolding when addTests=false even if generateTests is not true', () => {
    const factors = computeFeatureFactors({ addTests: false, generateTests: false });
    const testFactors = factors.filter(f => f.label.toLowerCase().includes('test'));
    assert.equal(testFactors.length, 0);
  });
});

describe('computeFeatureFactors — documentation', () => {
  it('adds docs delta 0.08 by default', () => {
    const factors = computeFeatureFactors({});
    const f = factors.find(f => f.label.toLowerCase().includes('documentation'));
    assert.ok(f, 'expected documentation factor');
    assert.equal(f.delta, 0.08);
  });

  it('omits documentation when addDocs=false', () => {
    const factors = computeFeatureFactors({ addDocs: false });
    const f = factors.find(f => f.label.toLowerCase().includes('documentation'));
    assert.equal(f, undefined);
  });
});

describe('computeFeatureFactors — linting', () => {
  it('adds linting delta 0.03 by default', () => {
    const factors = computeFeatureFactors({});
    const f = factors.find(f => f.label.toLowerCase().includes('lint'));
    assert.ok(f, 'expected linting factor');
    assert.equal(f.delta, 0.03);
  });

  it('omits linting when addLinter=false', () => {
    const factors = computeFeatureFactors({ addLinter: false });
    const f = factors.find(f => f.label.toLowerCase().includes('lint'));
    assert.equal(f, undefined);
  });
});

describe('computeFeatureFactors — repair loop', () => {
  it('adds repair loop delta 0.30 by default', () => {
    const factors = computeFeatureFactors({});
    const f = factors.find(f => f.label.toLowerCase().includes('repair'));
    assert.ok(f, 'expected repair loop factor');
    assert.equal(f.delta, 0.30);
  });

  it('omits repair loop when useRepair=false', () => {
    const factors = computeFeatureFactors({ useRepair: false });
    const f = factors.find(f => f.label.toLowerCase().includes('repair'));
    assert.equal(f, undefined);
  });
});

describe('computeFeatureFactors — extra iterations', () => {
  it('adds no extra-iteration factor when maxIterations <= 3', () => {
    for (const n of [1, 2, 3]) {
      const factors = computeFeatureFactors({ maxIterations: n });
      const f = factors.find(f => f.label.toLowerCase().includes('iteration'));
      assert.equal(f, undefined, `expected no extra-iteration factor for maxIterations=${n}`);
    }
  });

  it('adds 0.10 extra per iteration above 3', () => {
    // maxIterations=4 → 1 extra → delta 0.10
    const f4 = computeFeatureFactors({ maxIterations: 4 }).find(f => f.label.includes('4'));
    assert.ok(f4, 'expected extra-iteration factor for maxIterations=4');
    assert.equal(Math.round(f4.delta * 100), 10);

    // maxIterations=6 → 3 extra → delta 0.30
    const f6 = computeFeatureFactors({ maxIterations: 6 }).find(f => f.label.includes('6'));
    assert.ok(f6, 'expected extra-iteration factor for maxIterations=6');
    assert.equal(Math.round(f6.delta * 100), 30);
  });

  it('defaults to maxIterations=3 when not set (no extra-iteration factor)', () => {
    const factors = computeFeatureFactors({});
    const f = factors.find(f => f.label.toLowerCase().includes('iteration'));
    assert.equal(f, undefined);
  });
});

describe('computeFeatureFactors — quality gate', () => {
  it('adds no quality gate factor for qualityLevel 0 or 1', () => {
    for (const ql of [0, 1]) {
      const factors = computeFeatureFactors({ qualityLevel: ql });
      const f = factors.find(f => f.label.toLowerCase().includes('quality gate'));
      assert.equal(f, undefined, `expected no quality gate for qualityLevel=${ql}`);
    }
  });

  it('adds Q2 gate delta 0.18 for qualityLevel=2', () => {
    const factors = computeFeatureFactors({ qualityLevel: 2 });
    const f = factors.find(f => f.label.includes('Q2'));
    assert.ok(f, 'expected Q2 quality gate factor');
    assert.equal(f.delta, 0.18);
  });

  it('adds Q3 gate delta 0.38 for qualityLevel=3', () => {
    const factors = computeFeatureFactors({ qualityLevel: 3 });
    const f = factors.find(f => f.label.includes('Q3'));
    assert.ok(f, 'expected Q3 quality gate factor');
    assert.equal(f.delta, 0.38);
  });

  it('adds only one quality gate factor per run', () => {
    const f2 = computeFeatureFactors({ qualityLevel: 2 });
    const f3 = computeFeatureFactors({ qualityLevel: 3 });
    assert.equal(f2.filter(f => f.label.toLowerCase().includes('quality gate')).length, 1);
    assert.equal(f3.filter(f => f.label.toLowerCase().includes('quality gate')).length, 1);
  });
});

// ── featureMultiplierFromFactors ──────────────────────────────────────────────

describe('featureMultiplierFromFactors', () => {
  it('returns 1.0 for an empty factor list', () => {
    assert.equal(featureMultiplierFromFactors([]), 1.0);
  });

  it('returns 1.0 + sum of all deltas', () => {
    const factors = [{ label: 'A', delta: 0.20 }, { label: 'B', delta: 0.10 }];
    assert.equal(featureMultiplierFromFactors(factors), 1.30);
  });

  it('default config produces the expected multiplier', () => {
    // pipeline 0.30 + scaffolding 0.18 + docs 0.08 + lint 0.03 + repair 0.30 = 0.89
    const factors = computeFeatureFactors({});
    const mult = featureMultiplierFromFactors(factors);
    assert.equal(Math.round(mult * 100), 189);
  });

  it('all-features-on config produces a higher multiplier than default', () => {
    const defaultMult = featureMultiplierFromFactors(computeFeatureFactors({}));
    const fullMult = featureMultiplierFromFactors(computeFeatureFactors({
      generateTests: true,
      qualityLevel: 3,
      maxIterations: 5,
    }));
    assert.ok(fullMult > defaultMult, `full (${fullMult}) should exceed default (${defaultMult})`);
  });

  it('disabling all optional features gives only the pipeline overhead', () => {
    const factors = computeFeatureFactors({
      addTests: false,
      addDocs: false,
      addLinter: false,
      useRepair: false,
    });
    const mult = featureMultiplierFromFactors(factors);
    assert.equal(Math.round(mult * 100), 130);
  });
});
