/**
 * SET 43f — driving metric: substrate hit-rate + classKey coverage standing rule.
 */
import { describe, expect, it } from 'vitest';
import {
  collectReportEvidence,
  formatClassKeyCoverageRule,
  formatSubstrateHitRate,
  isReportEvidenceLine,
} from '../src/tier1/reportEvidence';
import {
  aggregateInstallStats,
  buildMetricsGrid,
  substrateHitRate,
  type RunRecordLike,
} from '../src/tier1/statsAggregate';

function rec(partial: RunRecordLike): RunRecordLike {
  return {
    id: partial.id ?? 'r1',
    ts: partial.ts ?? '2026-07-26T12:00:00.000Z',
    mode: partial.mode ?? 'rha',
    problems: partial.problems ?? 0,
    error: partial.error ?? null,
    metrics: partial.metrics ?? {},
    cost: partial.cost,
    ...partial,
  };
}

describe('formatSubstrateHitRate (SET 43f)', () => {
  it('pins the headline string shape', () => {
    const line = formatSubstrateHitRate({
      attempts: 40,
      fixes: 12,
      refusals: 10,
      noHandler: 18,
    });
    expect(line).toBe(
      'substrate hit-rate: 12/40 fixes (30%) · refusals 10 · no-handler 18',
    );
  });

  it('appends sorted per-stage lines when byStage is present', () => {
    const text = formatSubstrateHitRate({
      attempts: 40,
      fixes: 12,
      refusals: 10,
      noHandler: 18,
      byStage: {
        repair: { attempts: 30, fixes: 7 },
        build: { attempts: 10, fixes: 5 },
      },
    });
    expect(text).toBe(
      [
        'substrate hit-rate: 12/40 fixes (30%) · refusals 10 · no-handler 18',
        '  build: 5/10 fixes (50%)',
        '  repair: 7/30 fixes (23%)',
      ].join('\n'),
    );
  });

  it('is admitted by isReportEvidenceLine / collectReportEvidence', () => {
    const line = formatSubstrateHitRate({
      attempts: 40, fixes: 12, refusals: 10, noHandler: 18,
    });
    expect(isReportEvidenceLine(line)).toBe(true);
    const collected = collectReportEvidence([{ message: line }]);
    expect(collected.substrateHitRate).toBe(line);
    expect(collected.lines).toContain(line);
  });
});

describe('formatClassKeyCoverageRule (SET 43f)', () => {
  it('flags ≥3 uncovered and marks mechanical ok; ignores count<3', () => {
    const lines = formatClassKeyCoverageRule([
      { classKey: 'assertion', count: 12, stratum: 'mechanical' },
      { classKey: 'mock-API', count: 5, stratum: 'uncovered' },
      { classKey: 'throw-mismatch', count: 4, stratum: 'semantic-impossibility', note: 'needs runtime types' },
      { classKey: 'rare', count: 2, stratum: 'uncovered' },
      { classKey: 'ambiguity', count: 3, stratum: 'mechanical-candidate' },
    ]);
    expect(lines[0]).toMatch(/^classKey coverage:/);
    expect(lines).toContain('  assertion: 12 · mechanical');
    expect(lines).toContain('  mock-API: 5 · UNCOVERED');
    expect(lines).toContain('  throw-mismatch: 4 · semantic-impossibility — needs runtime types');
    expect(lines).toContain('  ambiguity: 3 · mechanical-candidate');
    expect(lines.some(l => l.includes('rare'))).toBe(false);
  });

  it('defaults missing stratum to UNCOVERED for count≥3', () => {
    const lines = formatClassKeyCoverageRule([
      { classKey: 'orphan', count: 3 },
    ]);
    expect(lines).toContain('  orphan: 3 · UNCOVERED');
  });

  it('admits header + indented rows into the collector', () => {
    const rows = formatClassKeyCoverageRule([
      { classKey: 'assertion', count: 12, stratum: 'mechanical' },
      { classKey: 'mock-API', count: 5, stratum: 'uncovered' },
    ]);
    expect(isReportEvidenceLine(rows[0]!)).toBe(true);
    expect(isReportEvidenceLine(rows[1]!)).toBe(true);
    const collected = collectReportEvidence(rows.map(message => ({ message })));
    expect(collected.classKeyCoverage).toEqual(rows);
    expect(collected.lines).toEqual(expect.arrayContaining(rows));
  });
});

describe('substrateHitRate facet (SET 43f)', () => {
  it('returns null when attempts≤0; else fixes/attempts', () => {
    expect(substrateHitRate(12, 40)).toBeCloseTo(0.3);
    expect(substrateHitRate(0, 0)).toBeNull();
    expect(substrateHitRate(5, -1)).toBeNull();
  });

  it('surfaces hit-rate on lifetime/latest facets and the metrics grid', () => {
    const summary = aggregateInstallStats([
      rec({
        id: 'a',
        ts: '2026-07-26T10:00:00.000Z',
        metrics: {
          substrateAttempts: 40, substrateFixes: 12,
          substrateRefusals: 10, substrateNoHandler: 18,
          mechanicalFixes: 12,
        },
      }),
      rec({
        id: 'b',
        ts: '2026-07-26T12:00:00.000Z',
        metrics: {
          substrateAttempts: 10, substrateFixes: 2,
          substrateRefusals: 1, substrateNoHandler: 0,
          mechanicalFixes: 2,
        },
      }),
    ]);
    // lifetime: (12+2)/(40+10) = 0.28; latest = run b = 2/10 = 0.2
    expect(summary.facets.substrateHitRate.lifetime).toBeCloseTo(0.28);
    expect(summary.facets.substrateHitRate.latest).toBeCloseTo(0.2);
    const grid = buildMetricsGrid(summary);
    const hit = grid.find(r => r.kind === 'row' && r.key === 'substrateHitRate');
    expect(hit?.kind).toBe('row');
    if (hit?.kind === 'row') {
      expect(hit.label).toBe('Hit-rate (fixes/attempts)');
      expect(hit.format).toBe('ratio');
      expect(hit.cells.lifetime).toBeCloseTo(0.28);
    }
  });
});
