/**
 * T5 acceptance: input-gate memory (cached clear verdicts), name dedup,
 * and RHA_GATE_ROUNDS cap.
 */
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { FeatureCandidate, JobState, LlmClient } from '../src/core/types';
import { runEncodingAndIR } from '../src/core/pipeline';
import { JobManager } from '../src/server/jobs';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');

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

function baseClient(overrides: Partial<LlmClient> & Pick<LlmClient, 'enumerateFeatures' | 'probeFeature'>): LlmClient {
  const boom = async () => { throw new Error('unexpected LLM call'); };
  return {
    encodeFeatures: boom as LlmClient['encodeFeatures'],
    repairFeatures: boom as LlmClient['repairFeatures'],
    fixStub: async () => 'export {}\n',
    generateTests: boom as LlmClient['generateTests'],
    fixTestCases: boom as LlmClient['fixTestCases'],
    generateImplementation: boom as LlmClient['generateImplementation'],
    repair: boom as LlmClient['repair'],
    ...overrides,
  };
}

describe('T5 input-gate memory and round cap', () => {
  let dir: string;
  const prevRounds = process.env.RHA_GATE_ROUNDS;
  const prevProbes = process.env.RHA_GATE_PROBES;

  beforeEach(() => {
    dir = path.join(ROOT, 'workspaces', `gate-t5-${Date.now().toString(36)}`);
    fs.mkdirSync(path.dirname(dir), { recursive: true });
    process.env.RHA_GATE_PROBES = '1';
  });

  afterEach(() => {
    fs.rmSync(dir, { recursive: true, force: true });
    if (prevRounds === undefined) delete process.env.RHA_GATE_ROUNDS;
    else process.env.RHA_GATE_ROUNDS = prevRounds;
    if (prevProbes === undefined) delete process.env.RHA_GATE_PROBES;
    else process.env.RHA_GATE_PROBES = prevProbes;
  });

  it('reuses cleared verdicts without re-probing', async () => {
    let probes = 0;
    const llm = baseClient({
      enumerateFeatures: async () => [
        { name: 'Clear Feature', scope: 'returns a code for a url' },
        { name: 'Vague Feature', scope: 'does something' },
      ],
      probeFeature: async (c) => {
        probes++;
        if (c.name === 'Clear Feature' || c.scope.includes('Clear Feature')) {
          return { clear: true, assertion: 'shorten(url) returns a short code string', question: '' };
        }
        return { clear: false, assertion: 'cannot tell', question: 'What does it do?' };
      },
    });

    const job = freshJob(dir);
    // Seed a prior clear verdict matching the candidate
    job.promptAssessment = {
      sufficient: false,
      featuresIdentified: ['Clear Feature'],
      deficiencies: [],
      suggestedRewrite: '',
      verdicts: [{ name: 'Clear Feature', scope: 'returns a code for a url', clear: true, assertion: 'shorten(url) returns a short code string' }],
    };

    await runEncodingAndIR(job, llm, () => {});
    expect(job.phase).toBe('insufficient-input');
    // Only Vague Feature should have been probed (Clear Feature cached)
    expect(probes).toBe(1);
    expect(job.promptAssessment!.verdicts!.find(v => v.name === 'Clear Feature')!.clear).toBe(true);
  });

  it('dedupes duplicate-name candidates to a single probe', async () => {
    let probes = 0;
    const dups: FeatureCandidate[] = [
      { name: 'Required Namespaces', scope: 'a' },
      { name: 'Required Namespaces', scope: 'b' },
      { name: 'Required Namespaces', scope: 'c' },
      { name: 'Required Namespaces', scope: 'd' },
    ];
    const llm = baseClient({
      enumerateFeatures: async () => dups,
      probeFeature: async () => {
        probes++;
        return { clear: false, assertion: 'cannot tell', question: 'What namespaces?' };
      },
    });

    const job = freshJob(dir);
    await runEncodingAndIR(job, llm, () => {});
    expect(probes).toBe(1);
  });

  it('RHA_GATE_ROUNDS=2 blocks the third reassess with no LLM calls', async () => {
    process.env.RHA_GATE_ROUNDS = '2';
    process.env.MOCK_LLM = '1';
    const mgr = new JobManager();
    const state = mgr.create('round-cap fixture prompt');
    const id = state.id;
    // Force a blocked gate without waiting on the async IR phase.
    state.phase = 'insufficient-input';
    state.promptAssessment = {
      sufficient: false,
      featuresIdentified: [],
      deficiencies: [{ aspect: 'x', issue: 'y', question: 'z' }],
      suggestedRewrite: '',
      verdicts: [],
    };
    state.gateRounds = 0;

    expect(mgr.reassess(id)).toEqual({ ok: true });
    state.phase = 'insufficient-input';
    expect(mgr.reassess(id)).toEqual({ ok: true });
    state.phase = 'insufficient-input';
    const third = mgr.reassess(id);
    expect(third.ok).toBe(false);
    expect(third.error).toMatch(/round cap reached/);
    expect(state.gateRounds).toBe(2);
    mgr.delete(id);
  });
});
