/**
 * SET 40a/40b — parse-gate meta-error discrimination + environment-split guard.
 */
import { afterEach, describe, expect, it } from 'vitest';
import type { GeneratedFile } from '../src/core/types';
import {
  checkTier1NoEval,
  TIER1_EVAL_ALLOWLIST,
} from '../scripts/check-tier1-no-eval';
import {
  GATE_UNAVAILABLE,
  gateOrParseMessage,
  isGateInfrastructureError,
  programParseError,
  programParseErrorAsync,
  setProgramParseProbe,
  vetProgramParse,
  vetProgramParseAfterWrite,
  vetProgramParseAfterWriteAsync,
  vetProgramParseAsync,
} from '../src/tier1/testExec';

function cleanMod(): GeneratedFile {
  return {
    path: 'src/m1.ts',
    content: 'export function m1(): number { return 1; }\n',
  };
}

function cleanSuite(): GeneratedFile {
  return {
    path: 'tests/f1.test.ts',
    content: [
      "import { describe, it, expect } from 'vitest';",
      "import { m1 } from '../src/m1';",
      "describe('t', () => { it('x', () => { expect(m1()).toBe(1); }); });",
    ].join('\n'),
  };
}

afterEach(() => {
  setProgramParseProbe(null);
});

describe('SET 40a GATE_UNAVAILABLE meta-error discrimination', () => {
  it('EvalError / unsafe-eval → GATE_UNAVAILABLE (not CSP text as parse failure)', () => {
    expect(isGateInfrastructureError(new EvalError('unsafe-eval'))).toBe(true);
    expect(gateOrParseMessage(new EvalError('Refused because unsafe-eval'))).toBe(GATE_UNAVAILABLE);
    expect(gateOrParseMessage(new Error('Refused to evaluate a string as JavaScript because \'unsafe-eval\' is not an allowed source'))).toBe(GATE_UNAVAILABLE);
    expect(gateOrParseMessage(new SyntaxError('Unexpected token'))).toBe('Unexpected token');
  });

  it('mock new Function throwing EvalError → vetProgramParse fail-OPEN with gateUnavailable', () => {
    const Orig = globalThis.Function;
    // @ts-expect-error — replace constructor for CSP simulation
    globalThis.Function = function () {
      throw new EvalError('Refused to evaluate a string as JavaScript because \'unsafe-eval\' is not an allowed source of script');
    };
    try {
      const artifact = [cleanMod(), cleanSuite()];
      const v = vetProgramParse(artifact, 'src/m1.ts');
      expect(v).toEqual({ ok: true, gateUnavailable: true });
      expect(v.ok).toBe(true);
      if (v.ok) expect(v.gateUnavailable).toBe(true);
      // Must NOT look like a candidate rejection.
      expect('message' in v && (v as { message?: string }).message).toBeFalsy();

      const after = vetProgramParseAfterWrite(cleanMod(), artifact);
      expect(after).toEqual({ ok: true, gateUnavailable: true });

      expect(programParseError(artifact)).toBe(GATE_UNAVAILABLE);
    } finally {
      globalThis.Function = Orig;
    }
  });

  it('GATE_UNAVAILABLE fail-open does not count as parse-poison rejection (vet ok)', () => {
    const Orig = globalThis.Function;
    // @ts-expect-error — CSP simulation
    globalThis.Function = function () {
      throw new EvalError('unsafe-eval');
    };
    try {
      const v = vetProgramParse([cleanMod(), cleanSuite()], 'src/m1.ts');
      // Integration increments parse-poison only on ok:false — fail-open stays ok.
      expect(v.ok).toBe(true);
      expect(v).toMatchObject({ ok: true, gateUnavailable: true });
    } finally {
      globalThis.Function = Orig;
    }
  });

  it('real SyntaxError still rejects (ok: false)', () => {
    const poison: GeneratedFile = {
      path: 'src/poison.ts',
      content: 'export const re = /(/;\n',
    };
    const artifact = [poison, cleanSuite()];
    const v = vetProgramParse(artifact, poison.path);
    expect(v.ok).toBe(false);
    if (!v.ok) {
      expect(v.message).not.toBe(GATE_UNAVAILABLE);
      expect(v.event).toMatch(/^program-parse regression: src\/poison\.ts rejected/);
    }
  });

  it('async probe path: sandbox probe used when set; GATE_UNAVAILABLE fail-open', async () => {
    setProgramParseProbe(async () => GATE_UNAVAILABLE);
    const artifact = [cleanMod(), cleanSuite()];
    const v = await vetProgramParseAsync(artifact, 'src/m1.ts');
    expect(v).toEqual({ ok: true, gateUnavailable: true });
    const after = await vetProgramParseAfterWriteAsync(cleanMod(), artifact);
    expect(after).toEqual({ ok: true, gateUnavailable: true });
    expect(await programParseErrorAsync(artifact)).toBe(GATE_UNAVAILABLE);
  });

  it('async probe path: null = parse ok; string = real failure', async () => {
    setProgramParseProbe(async () => null);
    expect(await vetProgramParseAsync([cleanMod(), cleanSuite()], 'src/m1.ts')).toEqual({ ok: true });

    setProgramParseProbe(async () => 'Unexpected token }');
    const bad = await vetProgramParseAsync([cleanMod(), cleanSuite()], 'src/m1.ts');
    expect(bad.ok).toBe(false);
    if (!bad.ok) expect(bad.message).toBe('Unexpected token }');
  });
});

describe('SET 40b environment-split static guard', () => {
  it('allowlists testExec.ts and fails other src/tier1 new Function/eval sites', () => {
    expect(TIER1_EVAL_ALLOWLIST.has('testExec.ts')).toBe(true);
    const r = checkTier1NoEval();
    expect(r.ok, r.violations.join('\n')).toBe(true);
    expect(r.violations).toEqual([]);
  });
});
