/**
 * SET 4 — harness contract (silent in) + capability scanner/gate (loud out).
 */
import { describe, expect, it } from 'vitest';
import { composeEncodePrompt, HARNESS_CONTRACT_SYSTEM } from '../src/core/prompts';
import type { FeatureSpec, LlmClient } from '../src/core/types';
import { buildRepo } from '../src/tier1/exportRepo';
import { runTier1Job } from '../src/tier1/runner';
import { scanCapabilities } from '../src/tier1/substrate';
import { makeEvalSuiteRunner } from '../src/tier1/testExec';

const WIKI_SNIPPET = [
  'Build a wiki clone.',
  '1. Viewport must be 1024px wide with a focus ring.',
  '2. Use axe-core for a11y audits.',
  '3. CREATE TABLE pages (id BIGSERIAL PRIMARY KEY, body tsvector);',
  '4. Redis sliding window rate limit.',
  '5. Serve favicon with Content-Type: image/png over TCP.',
  '6. SMTP email on publish.',
].join('\n');

function pureLibSpec(): FeatureSpec {
  return {
    title: 'Calc', prompt: 'pure calc library',
    features: [{
      id: 'F1', name: 'calc', description: 'adds numbers', parentId: null,
      deps: [], files: ['src/calc.ts'], testFile: 'tests/f1.test.ts',
      edgeCases: ['zero'], envVars: [],
      exports: [{ file: 'src/calc.ts', symbol: 'add' }],
      stubs: [{
        path: 'src/calc.ts',
        content: 'export function add(a: number, b: number): number { throw new Error("stub"); }\n',
      }],
    }],
  } as unknown as FeatureSpec;
}

function mockClient(overrides: Partial<LlmClient> & { seenEncode?: string[] } = {}): LlmClient & { seenEncode: string[]; fillCalls: number } {
  const seenEncode: string[] = overrides.seenEncode ?? [];
  const state = { fillCalls: 0 };
  const base = {
    seenEncode,
    get fillCalls() { return state.fillCalls; },
    async enumerateFeatures() { return []; },
    async probeFeature() { return { clear: true, assertion: 'works', question: '' }; },
    async encodeFeatures(p: string) {
      seenEncode.push(p);
      return pureLibSpec();
    },
    async repairFeatures() { return []; },
    async fixStub() { return ''; },
    async generateTests() {
      return { content: '', parts: { imports: [], cases: [] } };
    },
    async fixTestCases(_s: unknown, _f: unknown, broken: { name: string; kind: string }[]) {
      state.fillCalls++;
      return {
        cases: broken.map(b => ({
          name: b.name,
          kind: b.kind as 'behavior' | 'edge' | 'integration',
          body: b.kind === 'edge'
            ? 'expect(() => add(0, 0)).not.toThrow();'
            : 'expect(add(2, 3)).toBe(5);',
        })),
      };
    },
    async generateImplementation() {
      return [{ path: 'src/calc.ts', content: 'export function add(a: number, b: number): number { return a + b; }\n' }];
    },
    async repair() { return []; },
  };
  return Object.assign(base, overrides) as LlmClient & { seenEncode: string[]; fillCalls: number };
}

describe('SET 4a harness contract', () => {
  it('composeEncodePrompt appends contract last', () => {
    const out = composeEncodePrompt('build a calc', HARNESS_CONTRACT_SYSTEM);
    expect(out.startsWith('build a calc')).toBe(true);
    expect(out.endsWith(HARNESS_CONTRACT_SYSTEM)).toBe(true);
    expect(out).toContain('THIS CONTRACT WINS');
  });

  it('encoder sees contract; provenance/prompt stay raw', async () => {
    const llm = mockClient();
    const raw = 'build a pure calc library';
    const res = await runTier1Job({
      prompt: raw,
      encodePrompt: composeEncodePrompt(raw, HARNESS_CONTRACT_SYSTEM),
      contractVersion: 'V1.11+deadbeefdeadbeef',
      llm,
      runSuite: makeEvalSuiteRunner(),
    });
    expect(llm.seenEncode[0]).toContain('HARNESS CONTRACT');
    expect(llm.seenEncode[0]!.endsWith(HARNESS_CONTRACT_SYSTEM)).toBe(true);
    expect(res.provenance.every(p => !p.prompt.includes('HARNESS CONTRACT'))).toBe(true);
    expect(res.provenance.every(p => p.prompt === raw)).toBe(true);
    expect(res.contractVersion).toBe('V1.11+deadbeefdeadbeef');
  });
});

describe('SET 4b capability scanner', () => {
  it('detects WikiClone-style out-of-scope lines with capabilities', () => {
    const hits = scanCapabilities(WIKI_SNIPPET);
    const caps = new Set(hits.map(h => h.capability));
    expect(caps.has('computed-style') || caps.has('browser')).toBe(true);
    expect(caps.has('database')).toBe(true);
    expect(caps.has('redis')).toBe(true);
    expect(caps.has('network')).toBe(true);
    expect(hits.every(h => h.line == null || h.line >= 1)).toBe(true);
    expect(hits.some(h => h.item.includes('CREATE TABLE'))).toBe(true);
  });

  it('pure-library prompt yields zero hits', () => {
    expect(scanCapabilities(
      'A pure TypeScript library that adds and multiplies numbers.\nEdge: empty input, unicode, roundtrip.',
    )).toEqual([]);
  });
});

describe('SET 4c plan-time capability gate', () => {
  it('drops a planned case licensed by a deferred line — never model-drawn', async () => {
    const llm = mockClient();
    llm.encodeFeatures = async (p: string) => {
      llm.seenEncode.push(p);
      const s = pureLibSpec();
      s.features[0]!.edgeCases = [
        'CREATE TABLE sessions (id BIGSERIAL)',
        'zero',
      ];
      return s;
    };
    const events: string[] = [];
    const res = await runTier1Job({
      prompt: 'calc with CREATE TABLE sessions (id BIGSERIAL)',
      llm,
      runSuite: makeEvalSuiteRunner(),
      onEvent: e => events.push(e.message),
    });
    expect(events.some(m => m.includes('excluded:') && m.includes('database'))).toBe(true);
    expect(res.problems.some(p => p.includes('excluded') && p.includes('CREATE TABLE'))).toBe(true);
    // The excluded edge must not appear in the frozen suite.
    expect(res.frozenTests[0]?.content ?? '').not.toContain('CREATE TABLE');
    // Behavior + remaining edge may still draw; excluded edge never requested by name.
    expect(res.deferred?.some(d => d.capability === 'database')).toBe(true);
  });
});

describe('SET 4d TRACEABILITY excluded + contractVersion', () => {
  it('renders Excluded section and contract version', async () => {
    const llm = mockClient();
    const res = await runTier1Job({
      prompt: WIKI_SNIPPET,
      encodePrompt: composeEncodePrompt('pure calc only', HARNESS_CONTRACT_SYSTEM),
      contractVersion: 'V1.11+abcd1234abcd1234',
      rawPrompt: WIKI_SNIPPET,
      llm,
      runSuite: makeEvalSuiteRunner(),
    });
    const repo = await buildRepo(res);
    const text = await repo.fs.promises.readFile('/repo/TRACEABILITY.md', 'utf8') as string;
    expect(text).toContain('Harness contract version: `V1.11+abcd1234abcd1234`');
    expect(text).toContain('## Excluded (untested — no runtime capability)');
    expect(text).not.toContain('## Deferred items');
    expect(text).not.toMatch(/Tier 1 scope/);
    expect(text).toMatch(/database|redis|network|computed-style|browser/);
  });
});
