import { describe, expect, it } from "vitest";
import type {
  RegexExecutionRequest,
  RegexReplacementRequest,
} from "../../../model/match";
import {
  DotNetEngineAdapter,
  DOTNET_FLAVOUR_VERSION,
} from "./DotNetEngineAdapter";
import type { DotNetRegexExports } from "./dotnet-module";

function request(
  overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
  return {
    flavour: "dotnet",
    flavourVersion: DOTNET_FLAVOUR_VERSION,
    pattern: "(?<word>\\w+)",
    flags: ["g"],
    options: {},
    subject: "😀 café",
    captureMetadata: [],
    scanAll: true,
    maximumMatches: 10,
    maximumCaptureRows: 100,
    ...overrides,
  };
}

function exportsWith(result: unknown): DotNetRegexExports {
  return {
    RegexBridge: {
      Identity: () =>
        "dotnet-regex|10.0.10|System.Text.RegularExpressions|utf16|culture-invariant|replacement-parity",
      Run: () => JSON.stringify(result),
    },
  };
}

describe(".NET Regex adapter", () => {
  it("keeps native UTF-16 ranges and named captures", async () => {
    const adapter = new DotNetEngineAdapter(
      exportsWith({
        abi: 1,
        ok: true,
        groupCount: 1,
        groupNames: [[1, "word"]],
        matches: [{ span: [3, 7], captures: [[3, 7]] }],
        resultsTruncated: false,
      }),
    );

    const result = await adapter.execute(request());

    expect(result.engine).toEqual(
      expect.objectContaining({
        flavour: "dotnet",
        engineName: "System.Text.RegularExpressions",
        engineVersion: "10.0.10",
        offsetUnit: "utf16",
      }),
    );
    expect(result.matches[0]).toMatchObject({
      value: "café",
      range: { startUtf16: 3, endUtf16: 7 },
      nativeRange: { start: 3, end: 7, unit: "utf16" },
      captures: [{ groupNumber: 1, groupName: "word", value: "café" }],
    });
  });

  it("rejects a runtime with a mismatched identity", async () => {
    const adapter = new DotNetEngineAdapter({
      RegexBridge: {
        Identity: () => "dotnet-regex|9.0.0",
        Run: () => "{}",
      },
    });
    await expect(adapter.load()).rejects.toThrow("identity self-test");
  });

  it("decodes bounded UTF-8 replacement output and rejects invalid base64", async () => {
    const replacementRequest: RegexReplacementRequest = {
      ...request({ pattern: "." }),
      replacement: "😀",
      maximumOutputBytes: 4,
    };
    const adapter = new DotNetEngineAdapter(
      exportsWith({
        abi: 1,
        ok: true,
        groupCount: 0,
        groupNames: [],
        matches: [{ span: [3, 4], captures: [] }],
        resultsTruncated: false,
        outputBase64: "8J+YgA==",
        outputBytes: 4,
        outputTruncated: false,
      }),
    );

    await expect(adapter.replace(replacementRequest)).resolves.toMatchObject({
      output: "😀",
      outputBytes: 4,
      outputTruncated: false,
    });

    const invalid = new DotNetEngineAdapter(
      exportsWith({
        abi: 1,
        ok: true,
        outputBase64: "not base64",
        outputBytes: 4,
        outputTruncated: false,
      }),
    );
    await expect(invalid.replace(replacementRequest)).rejects.toThrow(
      /invalid base64/u,
    );
  });
});
