/**
 * End-to-end proof of the doctrine, in mock mode:
 *
 *   encode -> frozen IR -> build (3 injected bugs authenticated red in real
 *   time) -> RHA sweeps, localizes each bug to its node, repairs inside the
 *   boundary, re-verifies locally and at the parents -> full corpus green.
 *
 * Also asserts the structural invariants: the frozen IR is byte-identical
 * after healing, clean nodes are untouched, and the ledger records the whole
 * derivation with commit trailers.
 */
import { describe, expect, it, beforeAll, afterAll } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { JobState } from '../src/core/types';
import { runBuild, runEncodingAndIR } from '../src/core/pipeline';
import { applyRha } from '../src/core/rha';
import { sha256 } from '../src/core/sandbox';
import { MockLlm } from '../src/mock/mockLlm';
import type { Substrate } from '../src/core/substrate';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const WS = path.join(ROOT, 'workspaces', `e2e-${Date.now().toString(36)}`);

const events: { kind: string; data: Record<string, unknown> }[] = [];
const emit = (kind: string, data: Record<string, unknown> = {}) => events.push({ kind, data });

let job: JobState;
let substrate: Substrate;
let frozenHashes: Record<string, string> = {};
let storeShaAfterBuild = '';

beforeAll(() => {
  fs.mkdirSync(path.dirname(WS), { recursive: true });
});

afterAll(() => {
  fs.rmSync(WS, { recursive: true, force: true });
});

describe('full pipeline (mock llm)', () => {
  const usage: { op: string }[] = [];
  const llm = new MockLlm(r => usage.push(r));

  it('Phase A+B: encodes features and generates the IR artifact', async () => {
    job = {
      id: 'e2e', title: '', prompt: 'A URL shortener library with TTL and hit stats.',
      phase: 'created', createdAt: new Date().toISOString(), workspaceDir: WS,
      spec: null, manifest: null, buildResults: [], report: null, promptAssessment: null, irEscalations: [], error: null,
      events: [], llmUsage: [],
    };
    ({ substrate } = await runEncodingAndIR(job, llm, emit));

    expect(job.phase).toBe('awaiting-ir-approval');
    expect(job.spec!.features.map(f => f.id)).toEqual(['F1', 'F2', 'F3', 'F4']);
    expect(job.manifest!.cases.length).toBeGreaterThan(15);
    expect(job.manifest!.cases.some(c => c.kind === 'edge')).toBe(true);
    // IR exists on disk before any implementation does — strict input
    // isolation. src/ holds only header stubs at this point, and the suites
    // typechecked against them (cross-suite consistency gate).
    expect(fs.existsSync(path.join(WS, 'tests/f1-codec.test.ts'))).toBe(true);
    expect(fs.readFileSync(path.join(WS, 'src/codec.ts'), 'utf8')).toContain('stub: not implemented');
    expect(substrate.allRows().some(r => r.summary.includes('consistency gate'))).toBe(true);
    frozenHashes = { ...job.manifest!.freezeHashes };
    // ledger has spec/test nodes and forward dependency edges
    const rows = substrate.allRows();
    expect(rows.filter(r => r.type === 'test')).toHaveLength(4);
    expect(rows.filter(r => r.type === 'edge').length).toBeGreaterThanOrEqual(3);
  }, 120_000);

  it('Phase C: builds each feature and authenticates in real time (bugs surface)', async () => {
    await runBuild(job, substrate, llm, emit);
    expect(job.phase).toBe('awaiting-build-approval');

    const byId = new Map(job.buildResults.map(r => [r.featureId, r]));
    // injected bugs authenticated red
    expect(byId.get('F1')!.failed).toBeGreaterThan(0);   // off-by-one decode
    expect(byId.get('F3')!.failed).toBeGreaterThan(0);   // wiring bug crashes suite
    expect(byId.get('F4')!.failed).toBeGreaterThan(0);   // stub
    // the clean node authenticated green
    expect(byId.get('F2')!.failed).toBe(0);
    expect(byId.get('F2')!.passed).toBeGreaterThan(0);

    storeShaAfterBuild = sha256(fs.readFileSync(path.join(WS, 'src/store.ts'), 'utf8'));
  }, 240_000);

  it('Phase D: RHA localizes, repairs bounded, re-verifies parents, ends green', async () => {
    const report = await applyRha(WS, job.spec!, job.manifest!, substrate, llm, emit);

    expect(report.failed).toBe(0);
    expect(report.passed).toBeGreaterThan(15);
    expect(report.wholeArtifactRegenerated).toBe(false);

    // one healed episode per injected bug, upstream-most first
    const healed = report.episodes.filter(e => e.outcome === 'healed');
    expect(healed.map(e => e.featureId)).toEqual(['F1', 'F3', 'F4']);
    expect(healed[0].bugClass).toBe('test-failure');       // idiosyncratic
    expect(healed.some(e => e.bugClass === 'undefined-symbol')).toBe(true); // mechanical wiring
    expect(healed.some(e => e.bugClass === 'todo-stub')).toBe(true);        // committed stub

    // parent-failure-triggered recursion: repairing F1 rechecked F3 and found
    // it still red (its own wiring bug) — recorded, then healed as its own node
    expect(healed[0].parentRecheck).toBe('fail');

    // modifications deterministically localized — the clean node untouched
    const storeShaAfterRha = sha256(fs.readFileSync(path.join(WS, 'src/store.ts'), 'utf8'));
    expect(storeShaAfterRha).toBe(storeShaAfterBuild);
    expect(report.filesTouchedByRha.sort()).toEqual(['src/codec.ts', 'src/service.ts', 'src/stats.ts']);

    // the frozen IR was never modified
    for (const [file, hash] of Object.entries(frozenHashes)) {
      expect(sha256(fs.readFileSync(path.join(WS, file), 'utf8'))).toBe(hash);
    }

    // ledger recorded the healing derivation
    const rows = substrate.allRows();
    expect(rows.filter(r => r.type === 'error').length).toBeGreaterThanOrEqual(3);
    expect(rows.filter(r => r.type === 'repair').length).toBeGreaterThanOrEqual(3);
    const repairRow = rows.find(r => r.type === 'repair');
    expect(repairRow!.causeId).not.toBeNull(); // backward causal link to the error node

    // supergit sync: every repair commit carries the node trailer
    const log = substrate.git(['log', '--format=%B']);
    expect(log).toContain(`Supergit-Node: ${repairRow!.id}`);

    // a healed run escalates nothing, and every LLM call was metered. The
    // input gate is bounded: 1 enumerate + 3 per-feature probes (mock clears
    // each on the first draw) + 1 encodeFeatures = 5 encode-metered calls,
    // then 4 ir + 4 initial build + 3 fresh-from-contract heal builds (no
    // anchored repair() — doctrine: broken impl never in the prompt).
    expect(report.escalations).toEqual([]);
    expect(usage.map(u => u.op).sort()).toEqual(
      ['build', 'build', 'build', 'build', 'build', 'build', 'build',
       'encode', 'encode', 'encode', 'encode', 'encode',
       'ir', 'ir', 'ir', 'ir'],
    );
  }, 240_000);
});
