import { parseRegexTests } from "../../project/project.validation";
import {
  projectDocumentUtf8ByteLength,
  serializedProjectDocumentUtf8ByteLength,
} from "../../project/project-limits";
import type { RegexTestCase } from "./test-case.types";

export const MAXIMUM_REGEX_TEST_SUITE_FILE_BYTES = 32 * 1024 * 1024;

export interface RegexTestSuiteV1 {
  readonly schemaVersion: 1;
  readonly tests: readonly RegexTestCase[];
}

function assertSuiteDocumentWithinLimit(
  suite: RegexTestSuiteV1,
  maximumBytes: number,
  operation: "state" | "export",
): void {
  try {
    projectDocumentUtf8ByteLength(suite, maximumBytes);
  } catch (error) {
    if (error instanceof Error && error.message.includes("32 MiB")) {
      throw new Error(
        `Test suite exceeds the 32 MiB aggregate ${operation} limit.`,
        { cause: error },
      );
    }
    throw error;
  }
}

export function assertRegexTestSuiteWithinLimit(
  tests: readonly RegexTestCase[],
): void {
  assertSuiteDocumentWithinLimit(
    { schemaVersion: 1, tests },
    MAXIMUM_REGEX_TEST_SUITE_FILE_BYTES,
    "state",
  );
}

export function serializeRegexTestSuite(
  tests: readonly RegexTestCase[],
  includeSubjects: boolean,
): string {
  const suite: RegexTestSuiteV1 = {
    schemaVersion: 1,
    tests: includeSubjects
      ? tests
      : tests.map((test) => ({ ...test, subject: "" })),
  };
  assertSuiteDocumentWithinLimit(
    suite,
    MAXIMUM_REGEX_TEST_SUITE_FILE_BYTES - 1,
    "export",
  );
  const serialized = `${JSON.stringify(suite, null, 2)}\n`;
  try {
    serializedProjectDocumentUtf8ByteLength(
      serialized,
      MAXIMUM_REGEX_TEST_SUITE_FILE_BYTES,
    );
  } catch (error) {
    if (error instanceof Error && error.message.includes("32 MiB")) {
      throw new Error("Test-suite document exceeds the 32 MiB export limit.", {
        cause: error,
      });
    }
    throw error;
  }
  return serialized;
}

export function importRegexTestSuite(serialized: string): RegexTestSuiteV1 {
  try {
    serializedProjectDocumentUtf8ByteLength(
      serialized,
      MAXIMUM_REGEX_TEST_SUITE_FILE_BYTES,
    );
  } catch (error) {
    if (error instanceof Error && error.message.includes("32 MiB")) {
      throw new Error("Test-suite document exceeds the 32 MiB import limit.", {
        cause: error,
      });
    }
    throw error;
  }
  let value: unknown;
  try {
    value = JSON.parse(serialized);
  } catch (error) {
    throw new Error("Test-suite document is not valid JSON.", { cause: error });
  }
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error("Test-suite document must be a JSON object.");
  }
  const input = value as Record<string, unknown>;
  if (input.schemaVersion !== 1) {
    throw new Error(
      "Only regex-tools test-suite schema version 1 is supported.",
    );
  }
  return {
    schemaVersion: 1,
    tests: parseRegexTests(input.tests),
  };
}
