import { describe, expect, it } from "vitest";
import type { RegexEngineInfo } from "../model/flavour";
import type {
  CaptureResult,
  RegexExecutionResult,
  RegexMatchResult,
  RegexReplacementResult,
} from "../model/match";
import type { RegexTestExpectation } from "../tests/test-case.types";
import {
  compareSemanticResults,
  evaluateCaptureRangeMismatch,
  evaluateUnitTestFailure,
} from "./oracles";

const ENGINE: RegexEngineInfo = {
  flavour: "ecmascript",
  adapterVersion: "fixture",
  engineName: "Fixture",
  engineVersion: "1",
  offsetUnit: "utf16",
  capabilities: {
    compilation: true,
    matching: true,
    replacement: true,
    namedCaptures: true,
    captureHistory: false,
    actualTrace: false,
    benchmark: false,
  },
};

function capture(overrides: Partial<CaptureResult> = {}): CaptureResult {
  return {
    groupNumber: 1,
    groupName: "word",
    value: "a",
    status: "participated",
    range: { startUtf16: 0, endUtf16: 1 },
    nativeRange: { start: 0, end: 1, unit: "utf16" },
    ...overrides,
  };
}

function match(overrides: Partial<RegexMatchResult> = {}): RegexMatchResult {
  return {
    matchNumber: 1,
    value: "a",
    valueStatus: "complete",
    range: { startUtf16: 0, endUtf16: 1 },
    nativeRange: { start: 0, end: 1, unit: "utf16" },
    captures: [capture()],
    ...overrides,
  };
}

function execution(
  matches: readonly RegexMatchResult[] = [match()],
  overrides: Partial<RegexExecutionResult> = {},
): RegexExecutionResult {
  return {
    accepted: true,
    engine: ENGINE,
    flags: {
      userFlags: "g",
      effectiveFlags: "gd",
      internallyAddedIndicesFlag: true,
      internallyAddedGlobalFlag: false,
    },
    matches,
    diagnostics: [],
    elapsedMs: 2,
    truncated: false,
    ...overrides,
  };
}

function replacement(
  output: string,
  result = execution(),
): RegexReplacementResult {
  return {
    execution: result,
    output,
    outputBytes: output.length,
    outputTruncated: false,
    truncated: false,
  };
}

describe("unit-test failure oracle", () => {
  const cases: readonly {
    expectation: RegexTestExpectation;
    result: RegexExecutionResult;
    replacement?: RegexReplacementResult;
    fingerprint: string;
  }[] = [
    {
      expectation: { kind: "should-match" },
      result: execution([]),
      fingerprint: "expected-match:absent",
    },
    {
      expectation: { kind: "should-not-match" },
      result: execution(),
      fingerprint: "expected-no-match:present",
    },
    {
      expectation: { kind: "match-count", count: 2 },
      result: execution(),
      fingerprint: "match-count:mismatch",
    },
    {
      expectation: { kind: "full-match", matchIndex: 2, value: "a" },
      result: execution(),
      fingerprint: "full-match:missing",
    },
    {
      expectation: {
        kind: "capture",
        matchIndex: 0,
        groupName: "word",
        status: "matched-empty",
        value: "different",
      },
      result: execution(),
      fingerprint: "capture:status-mismatch",
    },
    {
      expectation: { kind: "replacement", expected: "expected" },
      result: execution(),
      replacement: replacement("actual"),
      fingerprint: "replacement:value-mismatch",
    },
    {
      expectation: { kind: "must-complete-within", milliseconds: 1 },
      result: execution(),
      fingerprint: "duration:above-limit",
    },
    {
      expectation: { kind: "must-time-out" },
      result: execution(),
      fingerprint: "timeout:unexpected-completion",
    },
  ];

  it.each(cases)(
    "dispatches $expectation.kind without weakening the failure class",
    ({ expectation, result, replacement: replacementResult, fingerprint }) => {
      const evaluated = evaluateUnitTestFailure(
        expectation,
        result,
        replacementResult,
        "a",
      );
      expect(evaluated.kind).toBe("failure");
      expect(evaluated.fingerprint).toContain(fingerprint);
    },
  );

  it("records capture participation and value failures independently", () => {
    const evaluated = evaluateUnitTestFailure(
      {
        kind: "capture",
        matchIndex: 0,
        groupNumber: 1,
        status: "matched-empty",
        value: "different",
      },
      execution(),
      undefined,
      "a",
    );

    expect(evaluated.fingerprint).toEqual([
      "capture:status-mismatch",
      "capture:value-mismatch",
    ]);
  });

  it("fails closed on truncated output", () => {
    const evaluated = evaluateUnitTestFailure(
      { kind: "should-not-match" },
      execution([], { truncated: true }),
      undefined,
      "",
    );

    expect(evaluated).toMatchObject({
      kind: "inconclusive",
      fingerprint: ["truncated-result"],
    });
  });
});

describe("capture-range and semantic comparison oracles", () => {
  it("preserves a wrong range by capture identity", () => {
    expect(
      evaluateCaptureRangeMismatch(
        {
          matchIndex: 0,
          groupName: "word",
          expectedRange: { startUtf16: 1, endUtf16: 1 },
        },
        execution(),
        "a",
      ),
    ).toMatchObject({
      kind: "failure",
      fingerprint: ["capture:range-mismatch"],
    });
  });

  it("compares normalized ranges, values and capture semantics but not native offsets or timing", () => {
    const right = execution(
      [
        match({
          nativeRange: { start: 0, end: 99, unit: "utf8-byte" },
          captures: [
            capture({
              nativeRange: { start: 0, end: 99, unit: "utf8-byte" },
            }),
          ],
        }),
      ],
      {
        engine: {
          ...ENGINE,
          flavour: "pcre2",
          engineName: "Another engine",
          offsetUnit: "utf8-byte",
        },
        elapsedMs: 999,
      },
    );

    expect(
      compareSemanticResults({ execution: execution() }, { execution: right }),
    ).toMatchObject({ kind: "same", mismatchKinds: [] });
  });

  it("reports exact mismatch categories including replacement output", () => {
    const rightExecution = execution([
      match({
        value: "b",
        range: { startUtf16: 0, endUtf16: 2 },
        captures: [
          capture({
            status: "matched-empty",
            range: { startUtf16: 1, endUtf16: 1 },
            value: "",
          }),
        ],
      }),
    ]);

    const compared = compareSemanticResults(
      { execution: execution(), replacement: replacement("left") },
      {
        execution: rightExecution,
        replacement: replacement("right", rightExecution),
      },
    );

    expect(compared.kind).toBe("mismatch");
    expect(compared.mismatchKinds).toEqual([
      "capture-range",
      "capture-status",
      "capture-value",
      "match-range",
      "match-value",
      "replacement-output",
    ]);
  });
});
