import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ExecutionContext } from "../../../../schema/src/records/context.js";
import {
  classify,
  type ClassifiedRun,
  type CoverageEvent,
  type DetectorOutcome,
  type KernelStores,
} from "../../../../core/src/classify/classifier.js";
import { loadContractStore } from "../../../../core/src/contracts/store.js";
import { loadDecisionStore } from "../../../../core/src/decisions/store.js";
import { parseClosureFixture } from "../../../../universal/src/primitives/closure/types.js";
import {
  evaluateClosureScenarios,
  flattenClosureViolations,
} from "./evaluate-closure.js";
import type { ClosureCheckInput, ClosureCheckResult, ClosureUniRule } from "./types.js";

const CLOSURE_DETECTOR_IDS: Record<ClosureUniRule, string> = {
  "UNI-050": "closure-uni-050",
  "UNI-051": "closure-uni-051",
  "UNI-052": "closure-uni-052",
  "UNI-053": "closure-uni-053",
  "UNI-054": "closure-uni-054",
  "UNI-080": "closure-uni-080",
  "UNI-081": "closure-uni-081",
  "UNI-082": "closure-uni-082",
  "UNI-083": "closure-uni-083",
  "UNI-084": "closure-uni-084",
};

function closureContext(fixtureId: string): ExecutionContext {
  return {
    kind: "package",
    surfaceId: fixtureId,
    adapterId: "closure-adapter",
    environment: {},
    seed: fixtureId,
  };
}

async function createDefaultStores(now = "2026-07-25T12:00:00.000Z"): Promise<KernelStores> {
  const root = await mkdtemp(join(tmpdir(), "invariantum-closure-adapter-"));
  const [decisions, contracts] = await Promise.all([
    loadDecisionStore(root),
    loadContractStore(root),
  ]);
  return {
    decisions,
    contracts,
    authoritativeContracts: new Set<string>(),
    now,
  };
}

function laneEligibilityForRule(rule: ClosureUniRule): "advisory" | "blocking-eligible" | "blocking" {
  if (rule === "UNI-054" || rule === "UNI-084") {
    return "advisory";
  }
  return "blocking";
}

function buildViolationOutcome(
  fixtureId: string,
  rule: ClosureUniRule,
  summary: string,
  violation: Record<string, unknown>,
  laneEligibility: "advisory" | "blocking-eligible" | "blocking",
): DetectorOutcome {
  const context = closureContext(fixtureId);
  return {
    detector: { id: CLOSURE_DETECTOR_IDS[rule], version: "1.0.0" },
    class: "closure-violation",
    severity: laneEligibility === "advisory" ? "medium" : "high",
    target: { kind: "closure-fixture", canonical: fixtureId },
    context,
    summary: `${rule}: ${summary}`,
    evidence: [
      {
        truthSource: laneEligibility === "advisory" ? "inferred" : "universal",
        payload: violation,
      },
    ],
    artifacts: [],
    laneEligibility,
    ...(laneEligibility !== "advisory" ? { proofConditionMet: true } : {}),
    scope: {
      id: `closure:${fixtureId}:${rule}`,
      detectorId: CLOSURE_DETECTOR_IDS[rule],
      surfaceId: fixtureId,
    },
    violation,
    contractOrConfig: { fixtureId, rule },
    contextDimensions: { rule },
  };
}

function buildCoverageOutcomes(
  fixtureId: string,
  selection: ClosureCheckInput["selection"],
): CoverageEvent[] {
  if (selection === undefined) {
    return [];
  }
  if (selection.matched > 0 || selection.expectedEmpty === true) {
    return [];
  }
  const context = closureContext(fixtureId);
  return Object.values(CLOSURE_DETECTOR_IDS).map((detectorId) => ({
    scope: {
      id: `closure:${fixtureId}:${detectorId}`,
      detectorId,
      surfaceId: fixtureId,
    },
    context,
    reason: "empty-selection" as const,
    witnessRefs: [],
  }));
}

async function classifyClosureViolations(
  fixtureId: string,
  violationsByRule: ReturnType<typeof evaluateClosureScenarios>,
  runId: string,
): Promise<ClassifiedRun> {
  const detectorOutcomes: DetectorOutcome[] = [];

  for (const [rule, violations] of Object.entries(violationsByRule) as Array<
    [ClosureUniRule, NonNullable<(typeof violationsByRule)[ClosureUniRule]>]
  >) {
    if (violations.length === 0) {
      continue;
    }
    const laneEligibility = laneEligibilityForRule(rule);
    detectorOutcomes.push(
      buildViolationOutcome(
        fixtureId,
        rule,
        violations[0]?.fact ?? "closure violation observed",
        { rule, violations },
        laneEligibility,
      ),
    );
  }

  const stores = await createDefaultStores();
  return classify({
    detectorOutcomes,
    harnessEvents: [],
    coverageEvents: [],
    stores,
    runId,
  });
}

function resolveLaneEligibility(
  violationsByRule: ReturnType<typeof evaluateClosureScenarios>,
  coverageOutcomes: CoverageEvent[],
): "advisory" | "blocking-eligible" | "blocking" {
  if (coverageOutcomes.length > 0) {
    return "advisory";
  }
  const rules = Object.keys(violationsByRule) as ClosureUniRule[];
  if (rules.length === 0) {
    return "advisory";
  }
  if (rules.some((rule) => laneEligibilityForRule(rule) === "blocking")) {
    return "blocking";
  }
  return "blocking-eligible";
}

function assertClosureCheckInput(input: ClosureCheckInput): void {
  if (input.fixtureId.trim().length === 0) {
    throw new Error("fixtureId must be a non-empty string");
  }
  if (input.closureVersion.trim().length === 0) {
    throw new Error("closureVersion must be a non-empty string");
  }
  if (!Array.isArray(input.scenarios) || input.scenarios.length === 0) {
    throw new Error("scenarios must be a non-empty array");
  }
}

export async function runClosureCheck(
  input: ClosureCheckInput,
  options: { runId?: string } = {},
): Promise<ClosureCheckResult> {
  assertClosureCheckInput(input);

  const universalScenarios = input.scenarios.filter(
    (scenario) => scenario.kind !== "environment-binding",
  );
  if (universalScenarios.length > 0) {
    parseClosureFixture({
      fixtureId: input.fixtureId,
      closureVersion: input.closureVersion,
      scenarios: universalScenarios,
      ...(input.intentionalException !== undefined
        ? { intentionalException: input.intentionalException }
        : {}),
    });
  }

  const filteredScenarios =
    input.intentionalException !== undefined
      ? input.scenarios.filter((scenario) => {
          const excepted = new Set(input.intentionalException?.rules ?? []);
          if (scenario.kind === "runtime-reference" && excepted.has("UNI-080")) {
            return false;
          }
          if (scenario.kind === "artifact-closure" && excepted.has("UNI-081")) {
            return false;
          }
          if (scenario.kind === "environment-binding" && excepted.has("UNI-082")) {
            return false;
          }
          if (scenario.kind === "inventory-provenance" && excepted.has("UNI-083")) {
            return false;
          }
          return true;
        })
      : input.scenarios;

  const violationsByRule = evaluateClosureScenarios(filteredScenarios, input.lifecycle);
  const violations = flattenClosureViolations(violationsByRule);
  const coverageOutcomes = buildCoverageOutcomes(input.fixtureId, input.selection);
  const runId = options.runId ?? `closure:${input.fixtureId}`;
  const classified = await classifyClosureViolations(input.fixtureId, violationsByRule, runId);

  return {
    fixtureId: input.fixtureId,
    holds: violations.length === 0 && coverageOutcomes.length === 0,
    violations,
    violationsByRule,
    coverageOutcomes,
    classified,
    laneEligibility: resolveLaneEligibility(violationsByRule, coverageOutcomes),
  };
}
