import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { generateHealthScorecard, topActions } from '../../scanners/lib/scoring.mjs';

const SAMPLE_AREA_SCORES = {
  areas: [
    { id: 'claude_md', name: 'CLAUDE.md', grade: 'A', score: 100, findingCount: 0 },
    { id: 'settings', name: 'Settings', grade: 'A', score: 90, findingCount: 1 },
    { id: 'hooks', name: 'Hooks', grade: 'A', score: 100, findingCount: 0 },
    { id: 'feature_coverage', name: 'Feature Coverage', grade: 'D', score: 30, findingCount: 17 },
  ],
  overallGrade: 'A',
};

const SAMPLE_GAP_FINDINGS = [
  {
    id: 'CA-GAP-001',
    scanner: 'GAP',
    severity: 'medium',
    title: 'No CLAUDE.md file',
    description: 'No project instructions file detected.',
    recommendation: 'Create a CLAUDE.md file with project-specific guidance.',
    category: 't1',
    file: null,
  },
  {
    id: 'CA-GAP-002',
    scanner: 'GAP',
    severity: 'medium',
    title: 'No permissions configured',
    description: 'No permissions block in settings.',
    recommendation: 'Add a permissions block to settings.json.',
    category: 't1',
    file: null,
  },
  {
    id: 'CA-GAP-003',
    scanner: 'GAP',
    severity: 'low',
    title: 'No status line configured',
    description: 'No status line.',
    recommendation: 'Add a status line.',
    category: 't3',
    file: null,
  },
];

describe('generateHealthScorecard signature change (3-param)', () => {
  it('2-arg call: backwards-compatible (humanized defaults to false)', () => {
    const out = generateHealthScorecard(SAMPLE_AREA_SCORES, 17);
    assert.equal(typeof out, 'string');
    assert.ok(out.length > 0);
    assert.ok(out.includes('Config-Audit Health Score'),
      'non-humanized scorecard should contain v5.0.0 title');
  });

  it('3-arg call with {humanized: false}: byte-equal to 2-arg call', () => {
    const twoArg = generateHealthScorecard(SAMPLE_AREA_SCORES, 17);
    const threeArgFalse = generateHealthScorecard(SAMPLE_AREA_SCORES, 17, { humanized: false });
    assert.equal(threeArgFalse, twoArg, 'options.humanized=false must produce identical output to 2-arg call');
  });

  it('3-arg call with {humanized: true}: differs from non-humanized', () => {
    const nonHumanized = generateHealthScorecard(SAMPLE_AREA_SCORES, 17, { humanized: false });
    const humanized = generateHealthScorecard(SAMPLE_AREA_SCORES, 17, { humanized: true });
    assert.notEqual(humanized, nonHumanized,
      'humanized=true must produce different output from humanized=false');
  });

  it('3-arg call with {humanized: true}: contains user-friendly phrasing', () => {
    const humanized = generateHealthScorecard(SAMPLE_AREA_SCORES, 17, { humanized: true });
    // Must contain at least one humanized cue distinguishing it from v5.0.0 prose
    const hasGradeContext = /healthy|good shape|attention|polish|setup/i.test(humanized);
    assert.ok(hasGradeContext,
      `humanized scorecard must include user-friendly grade context, got:\n${humanized}`);
  });

  it('preserves area names and scores in both modes', () => {
    const nonHumanized = generateHealthScorecard(SAMPLE_AREA_SCORES, 17, { humanized: false });
    const humanized = generateHealthScorecard(SAMPLE_AREA_SCORES, 17, { humanized: true });
    for (const area of SAMPLE_AREA_SCORES.areas.filter(a => a.name !== 'Feature Coverage')) {
      assert.ok(nonHumanized.includes(area.name),
        `non-humanized scorecard must include area name "${area.name}"`);
      assert.ok(humanized.includes(area.name),
        `humanized scorecard must include area name "${area.name}"`);
      assert.ok(nonHumanized.includes(`(${area.score})`),
        `non-humanized scorecard must include score (${area.score})`);
      assert.ok(humanized.includes(`(${area.score})`),
        `humanized scorecard must include score (${area.score})`);
    }
  });

  it('opportunity count handling in humanized mode', () => {
    const humanizedZero = generateHealthScorecard(SAMPLE_AREA_SCORES, 0, { humanized: true });
    const humanizedMany = generateHealthScorecard(SAMPLE_AREA_SCORES, 17, { humanized: true });
    assert.ok(humanizedMany.includes('17'), 'humanized scorecard must include opportunity count');
    // Both paths must remain finite strings
    assert.equal(typeof humanizedZero, 'string');
    assert.equal(typeof humanizedMany, 'string');
  });
});

describe('topActions humanizer support', () => {
  it('1-arg call: returns raw recommendations (backwards-compatible)', () => {
    const actions = topActions(SAMPLE_GAP_FINDINGS);
    assert.equal(actions.length, 3);
    assert.equal(actions[0], 'Create a CLAUDE.md file with project-specific guidance.');
    assert.equal(actions[1], 'Add a permissions block to settings.json.');
    assert.equal(actions[2], 'Add a status line.');
  });

  it('2-arg call with {humanized: false}: identical to 1-arg call', () => {
    const oneArg = topActions(SAMPLE_GAP_FINDINGS);
    const twoArg = topActions(SAMPLE_GAP_FINDINGS, { humanized: false });
    assert.deepStrictEqual(twoArg, oneArg);
  });

  it('2-arg call with {humanized: true}: at least one recommendation differs', () => {
    const raw = topActions(SAMPLE_GAP_FINDINGS, { humanized: false });
    const humanized = topActions(SAMPLE_GAP_FINDINGS, { humanized: true });
    assert.equal(humanized.length, raw.length, 'array length preserved');
    // The humanizer's GAP TRANSLATIONS replace at least one recommendation (No CLAUDE.md → "Add the file…")
    const anyDiffer = humanized.some((r, i) => r !== raw[i]);
    assert.ok(anyDiffer,
      `humanized=true must change at least one recommendation. raw=${JSON.stringify(raw)} humanized=${JSON.stringify(humanized)}`);
  });

  it('preserves ordering by tier (t1 → t2 → t3)', () => {
    const humanized = topActions(SAMPLE_GAP_FINDINGS, { humanized: true });
    assert.equal(humanized.length, 3);
    // 1st & 2nd: t1 findings, 3rd: t3 finding (t2 absent in sample)
    // Both modes preserve this ordering.
  });
});
