/**
 * SET 92g — mutation coverage for test-rha disposition emits (doctrine 15).
 *
 * Suppress each new emit site via `_suppressEmit` and assert the matching
 * 92f log/conservation assertion fails. Counters still increment (emit hook
 * only drops the log line) — proving the emit is load-bearing for observability.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureSpec, LlmClient, RepairInput } from '../src/core/types';
import { runTier1Job } from '../src/tier1/runner';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';

const RIGHT = 'export function add(a: number, b: number): number { return a + b; }';
const GOOD_A = 'expect(add(2, 3)).toBe(5);';
const GOOD_B = 'expect(add(7, 0)).toBe(7);';
const SICK_B = 'expect((add as unknown as { create: (n: number) => number }).create(7)).toBe(7);';

function makeSpec(): FeatureSpec {
  return {
    title: 'Adder', prompt: 'adder',
    features: [{
      id: 'F1', name: 'add', description: 'adds two numbers', parentId: null,
      deps: [], files: ['src/adder.ts'], testFile: 'tests/f1-add.test.ts',
      edgeCases: ['zero'], envVars: [],
      exports: [{ file: 'src/adder.ts', symbol: 'add' }],
      stubs: [{
        path: 'src/adder.ts',
        content: `export function add(a: number, b: number): number { throw new Error('stub: not implemented'); }`,
      }],
    }],
  } as unknown as FeatureSpec;
}

class MutationLlm {
  private rescueAttempt = 0;
  constructor(private readonly mode: 'throw-then-clear' | 'always-reject') {}

  async encodeFeatures() { return makeSpec(); }
  async generateTests() { return { content: '', parts: { imports: [], cases: [] } }; }
  async generateImplementation() { return [{ path: 'src/adder.ts', content: RIGHT }]; }
  async repair(_i: RepairInput) { return [{ path: 'src/adder.ts', content: RIGHT }]; }

  async fixTestCases(
    _s: unknown, _f: unknown, req: { name: string }[], _imp?: unknown, _cur?: unknown,
    opts?: { purpose?: string },
  ) {
    if (opts?.purpose?.startsWith('test-rha:')) {
      this.rescueAttempt++;
      if (this.mode === 'always-reject') {
        return { cases: [{ name: req[0]!.name, kind: 'edge' as const, body: '' }] };
      }
      if (this.rescueAttempt === 1) {
        return { cases: [{ name: req[0]!.name, kind: 'edge' as const, body: SICK_B }] };
      }
      if (this.rescueAttempt === 2) {
        throw new Error('mutation throw on attempt 2');
      }
      return { cases: [{ name: req[0]!.name, kind: 'edge' as const, body: GOOD_B }] };
    }
    const name = req[0]!.name;
    return {
      cases: [{
        name,
        kind: (name === 'zero' ? 'edge' : 'behavior') as 'edge' | 'behavior',
        body: name === 'zero' ? SICK_B : GOOD_A,
      }],
    };
  }
}

describe('SET 92g — emit mutation coverage', () => {
  it('suppressing draw-threw emit hides the line while the counter still fires', async () => {
    const res = await runTier1Job({
      prompt: 'adder',
      llm: new MutationLlm('throw-then-clear') as unknown as LlmClient,
      runSuite: makeEvalSuiteRunner(),
      _suppressEmit: /test-rha: draw threw/,
    });
    expect(res.metrics.testRhaDrawThrew).toBeGreaterThanOrEqual(1);
    expect(res.log.some(e => /test-rha: draw threw/.test(e.message))).toBe(false);
  });

  it('suppressing draft-rejected emit hides the line while the counter still fires', async () => {
    const res = await runTier1Job({
      prompt: 'adder',
      llm: new MutationLlm('always-reject') as unknown as LlmClient,
      runSuite: makeEvalSuiteRunner(),
      _suppressEmit: /test-rha: draft rejected/,
    });
    expect(res.metrics.testRhaDraftRejected).toBeGreaterThanOrEqual(1);
    expect(res.log.some(e => /test-rha: draft rejected/.test(e.message))).toBe(false);
  });

  it('suppressing draft-accepted emit hides the line while cleared still increments', async () => {
    const res = await runTier1Job({
      prompt: 'adder',
      llm: new MutationLlm('throw-then-clear') as unknown as LlmClient,
      runSuite: makeEvalSuiteRunner(),
      _suppressEmit: /test-rha: draft accepted/,
    });
    expect(res.metrics.testRhaDraftAcceptedCleared
      + res.metrics.testRhaDraftAcceptedStillSick).toBeGreaterThanOrEqual(1);
    expect(res.log.some(e => /test-rha: draft accepted/.test(e.message))).toBe(false);
  });

  it('suppressing case-cleared emit hides the line while the counter still fires', async () => {
    const res = await runTier1Job({
      prompt: 'adder',
      llm: new MutationLlm('throw-then-clear') as unknown as LlmClient,
      runSuite: makeEvalSuiteRunner(),
      _suppressEmit: /test-rha: case "zero" cleared/,
    });
    expect(res.metrics.testRhaCaseCleared).toBeGreaterThanOrEqual(1);
    expect(res.log.some(e => /test-rha: case "zero" cleared/.test(e.message))).toBe(false);
  });

  it('suppressing still-sick emit hides the line while the counter still fires', async () => {
    const res = await runTier1Job({
      prompt: 'adder',
      llm: new MutationLlm('throw-then-clear') as unknown as LlmClient,
      runSuite: makeEvalSuiteRunner(),
      _suppressEmit: /still sick — continuing ladder/,
    });
    expect(res.metrics.testRhaDraftAcceptedStillSick).toBeGreaterThanOrEqual(1);
    expect(res.log.some(e => /still sick — continuing ladder/.test(e.message))).toBe(false);
  });

  it('suppressing skipped-no-handle emit hides the line while the counter still fires', async () => {
    const base = makeEvalSuiteRunner();
    const res = await runTier1Job({
      prompt: 'adder',
      llm: new MutationLlm('always-reject') as unknown as LlmClient,
      runSuite: async (stubs, test) => {
        const real = await base(stubs, test);
        if (real.failures.some(f => /is not a function|create/.test(f.message))) {
          return {
            ...real,
            failures: [
              { name: '__orphan_no_case__', message: 'orphan.create is not a function' },
            ],
          };
        }
        return real;
      },
      _suppressEmit: /skipped — no case handle/,
    });
    expect(res.metrics.testRhaSkippedNoHandle).toBeGreaterThanOrEqual(1);
    expect(res.log.some(e => /skipped — no case handle/.test(e.message))).toBe(false);
  });
});
