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 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 { parseProtocolFixture } from "../../../../universal/src/primitives/protocol/types.js";
import {
  evaluateProtocolScenarios,
  flattenProtocolViolations,
} from "./evaluate-protocol.js";
import type { ProtocolCheckInput, ProtocolCheckResult, ProtocolUniRule } from "./types.js";

const PROTOCOL_DETECTOR_IDS: Record<ProtocolUniRule, string> = {
  "UNI-060": "protocol-uni-060",
  "UNI-061": "protocol-uni-061",
  "UNI-062": "protocol-uni-062",
  "UNI-063": "protocol-uni-063",
};

const PROTOCOL_DETECTOR = { id: "protocol-adapter", version: "1.0.0" } as const;

function protocolContext(fixtureId: string): ExecutionContext {
  return {
    kind: "stream",
    surfaceId: fixtureId,
    adapterId: "protocol-adapter",
    environment: {},
    seed: fixtureId,
  };
}

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

function laneEligibilityForRule(rule: ProtocolUniRule): "advisory" | "blocking-eligible" | "blocking" {
  if (rule === "UNI-061") {
    return "blocking-eligible";
  }
  return "blocking";
}

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

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

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

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

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

function assertProtocolCheckInput(input: ProtocolCheckInput): void {
  if (input.fixtureId.trim().length === 0) {
    throw new Error("fixtureId must be a non-empty string");
  }
  if (input.protocolVersion.trim().length === 0) {
    throw new Error("protocolVersion 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 runProtocolCheck(
  input: ProtocolCheckInput,
  options: { runId?: string } = {},
): Promise<ProtocolCheckResult> {
  assertProtocolCheckInput(input);

  const universalScenarios = input.scenarios.filter(
    (scenario) => scenario.kind !== "transfer-validation",
  );
  const parsed =
    universalScenarios.length > 0
      ? parseProtocolFixture({
          fixtureId: input.fixtureId,
          protocolVersion: input.protocolVersion,
          scenarios: universalScenarios,
          ...(input.intentionalException !== undefined
            ? { intentionalException: input.intentionalException }
            : {}),
        })
      : {
          fixtureId: input.fixtureId,
          protocolVersion: input.protocolVersion,
          scenarios: [],
          intentionalException: input.intentionalException,
        };

  if (parsed.intentionalException !== undefined) {
    const excepted = new Set(parsed.intentionalException.rules);
    const filteredScenarios = input.scenarios.filter((scenario) => {
      if (scenario.kind === "stream-decode" && excepted.has("UNI-060")) {
        return false;
      }
      if (scenario.kind === "stdout-purity" && excepted.has("UNI-061")) {
        return false;
      }
      if (scenario.kind === "serialization-round-trip" && excepted.has("UNI-062")) {
        return false;
      }
      if (scenario.kind === "transfer-validation" && excepted.has("UNI-063")) {
        return false;
      }
      return true;
    });
    const violationsByRule = evaluateProtocolScenarios(filteredScenarios);
    const violations = flattenProtocolViolations(violationsByRule);
    const runId = options.runId ?? `protocol:${input.fixtureId}`;
    const classified = await classifyProtocolViolations(input.fixtureId, violationsByRule, runId);
    return {
      fixtureId: input.fixtureId,
      holds: violations.length === 0,
      violations,
      violationsByRule,
      classified,
      laneEligibility: resolveLaneEligibility(violationsByRule),
    };
  }

  const violationsByRule = evaluateProtocolScenarios(input.scenarios);
  const violations = flattenProtocolViolations(violationsByRule);
  const runId = options.runId ?? `protocol:${input.fixtureId}`;
  const classified = await classifyProtocolViolations(input.fixtureId, violationsByRule, runId);

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