/**
 * T1 acceptance: gate re-entry must reopen the substrate (not re-init),
 * preserving the append-only ledger across rounds; autopilot overrides are
 * attributed distinctly from human overrides.
 */
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import type { JobState, LlmClient } from '../src/core/types';
import { runEncodingAndIR } from '../src/core/pipeline';
import { Substrate } from '../src/core/substrate';
import { MockLlm } from '../src/mock/mockLlm';

class GateFailLlm extends MockLlm {
  async enumerateFeatures() {
    return [{ name: 'Ambiguous Thing', scope: 'something vague' }];
  }
  async probeFeature() {
    return { clear: false, assertion: 'cannot tell', question: 'What should it do?' };
  }
}

function freshJob(dir: string): JobState {
  return {
    id: 'gate-t1',
    title: '',
    prompt: 'build something ambiguous',
    phase: 'created',
    createdAt: new Date().toISOString(),
    workspaceDir: dir,
    spec: null,
    manifest: null,
    buildResults: [],
    report: null,
    promptAssessment: null,
    irEscalations: [],
    error: null,
    events: [],
    llmUsage: [],
  };
}

describe('T1 substrate open on gate re-entry', () => {
  let dir: string;

  beforeEach(() => {
    dir = fs.mkdtempSync(path.join(os.tmpdir(), 'rha-gate-t1-'));
  });

  afterEach(() => {
    fs.rmSync(dir, { recursive: true, force: true });
  });

  it('reassess twice keeps both gate-fail nodes and exactly one genesis', async () => {
    const llm = new GateFailLlm() as unknown as LlmClient;
    const emit = () => {};
    const job = freshJob(dir);

    const { substrate: s1 } = await runEncodingAndIR(job, llm, emit);
    expect(job.phase).toBe('insufficient-input');
    const afterFirst = s1.allRows().filter(r => r.summary.includes('input gate: prompt is not feature-demarcated'));
    expect(afterFirst).toHaveLength(1);

    job.phase = 'created';
    job.error = null;
    const { substrate: s2 } = await runEncodingAndIR(job, llm, emit);
    expect(job.phase).toBe('insufficient-input');

    job.phase = 'created';
    job.error = null;
    const { substrate: s3 } = await runEncodingAndIR(job, llm, emit);
    expect(job.phase).toBe('insufficient-input');

    const rows = s3.allRows();
    const genesis = rows.filter(r => r.type === 'genesis');
    expect(genesis).toHaveLength(1);
    expect(genesis[0].step).toBe(1);

    const gateFails = rows.filter(r => r.summary.includes('input gate: prompt is not feature-demarcated'));
    expect(gateFails).toHaveLength(3); // initial + 2 reassess rounds
    expect(gateFails.map(r => r.step)).toEqual([...gateFails.map(r => r.step)].sort((a, b) => a - b));

    const log = execFileSync('git', ['log', '--oneline'], { cwd: dir, encoding: 'utf8' });
    const genesisCommits = log.split('\n').filter(l => /genesis/i.test(l));
    expect(genesisCommits).toHaveLength(1);

    // open path used — substrate identity differs but ledger is shared
    expect(s1.dir).toBe(s2.dir);
    expect(fs.existsSync(path.join(dir, '.supergit', 'ledger.csv'))).toBe(true);
  });

  it('autopilot override records AUTOPILOT and never "manual authentication"', async () => {
    const llm = new GateFailLlm() as unknown as LlmClient;
    const events: { kind: string; data: Record<string, unknown> }[] = [];
    const emit = (kind: string, data: Record<string, unknown> = {}) => {
      events.push({ kind, data });
    };
    const job = freshJob(dir);

    await runEncodingAndIR(job, llm, emit);
    expect(job.phase).toBe('insufficient-input');

    job.phase = 'created';
    const { substrate } = await runEncodingAndIR(job, llm, emit, {
      skipGate: true,
      overrideBy: 'autopilot',
    });

    const ov = substrate.allRows().find(r => r.summary.includes('AUTOPILOT override'));
    expect(ov).toBeTruthy();
    expect(ov!.summary).not.toMatch(/manual authentication/);
    expect(substrate.allRows().filter(r => r.type === 'genesis')).toHaveLength(1);

    const go = events.find(e => e.kind === 'gate-override');
    expect(go?.data.by).toBe('autopilot');

    const log = execFileSync('git', ['-C', dir, 'log', '--format=%s'], { encoding: 'utf8' });
    expect(log).toMatch(/input gate: autopilot override \(unsupervised\)/);
    expect(log).not.toMatch(/human override \(operator authenticated\)/);
    expect(log).not.toMatch(/manual authentication/);
  });

  it('human override keeps the manual-authentication wording', async () => {
    Substrate.init(dir);
    const llm = new MockLlm() as unknown as LlmClient;
    const events: { kind: string; data: Record<string, unknown> }[] = [];
    const job = freshJob(dir);
    const { substrate } = await runEncodingAndIR(job, llm, (kind, data = {}) => {
      events.push({ kind, data });
    }, {
      skipGate: true,
      overrideBy: 'human',
    });
    const ov = substrate.allRows().find(r => r.summary.includes('human operator override'));
    expect(ov!.summary).toContain('manual authentication');
    expect(ov!.summary).not.toContain('AUTOPILOT');
    expect(events.find(e => e.kind === 'gate-override')?.data.by).toBe('human');

    const log = execFileSync('git', ['-C', dir, 'log', '--format=%s'], { encoding: 'utf8' });
    expect(log).toMatch(/input gate: human override \(operator authenticated\)/);
  });
});
