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 { validateNegativeSecurityJourneySpec } from "./generate-negative-journeys.js";
import {
  type NegativeSecurityJourneyResult,
  type NegativeSecurityJourneySpec,
  type SecurityBoundarySystem,
  type SecurityCoverageGap,
  type SecuritySideEffect,
} from "./types.js";

const SECURITY_DETECTOR = { id: "security-boundary", version: "1.0.0" } as const;

function securityContext(systemId: string, journeyId: string): ExecutionContext {
  return {
    kind: "api",
    surfaceId: systemId,
    adapterId: "security-boundary",
    environment: {},
    seed: `${systemId}:${journeyId}`,
  };
}

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

function cloneSideEffects(effects: SecuritySideEffect[]): SecuritySideEffect[] {
  return effects.map((effect) => ({
    id: effect.id,
    kind: effect.kind,
    payload: { ...effect.payload },
  }));
}

function buildViolationOutcome(
  systemId: string,
  spec: NegativeSecurityJourneySpec,
  summary: string,
  violation: Record<string, unknown>,
  laneEligibility: "advisory" | "blocking-eligible",
): DetectorOutcome {
  const context = securityContext(systemId, spec.id);
  return {
    detector: SECURITY_DETECTOR,
    class: "security-boundary-violation",
    severity: laneEligibility === "blocking-eligible" ? "high" : "medium",
    target: { kind: "security-journey", canonical: spec.id },
    context,
    summary,
    evidence: [
      {
        truthSource: laneEligibility === "blocking-eligible" ? "universal" : "inferred",
        payload: violation,
      },
    ],
    artifacts: [],
    laneEligibility,
    ...(laneEligibility === "blocking-eligible" ? { proofConditionMet: true } : {}),
    scope: {
      id: `security:${systemId}:${spec.kind}:${spec.id}`,
      detectorId: SECURITY_DETECTOR.id,
      surfaceId: systemId,
    },
    violation,
    contractOrConfig: { journeyId: spec.id, kind: spec.kind, status: spec.status },
    contextDimensions: { kind: spec.kind },
  };
}

async function classifySecurityJourney(input: {
  systemId: string;
  spec: NegativeSecurityJourneySpec;
  denied: boolean;
  sideEffects: SecuritySideEffect[];
  violated: string[];
  coverageGap?: SecurityCoverageGap;
  runId: string;
}): Promise<ClassifiedRun> {
  const detectorOutcomes: DetectorOutcome[] = [];
  const coverageEvents: CoverageEvent[] = [];
  const laneEligibility =
    input.spec.status === "confirmed" && input.violated.length > 0
      ? "blocking-eligible"
      : "advisory";

  if (input.coverageGap !== undefined) {
    coverageEvents.push({
      scope: {
        id: `security:${input.systemId}:${input.spec.id}`,
        detectorId: SECURITY_DETECTOR.id,
        surfaceId: input.systemId,
      },
      context: securityContext(input.systemId, input.spec.id),
      reason: "unproven-precondition",
      witnessRefs: [{ id: `security-auth-fixture:${input.spec.id}` }],
    });
  } else if (input.violated.length > 0) {
    detectorOutcomes.push(
      buildViolationOutcome(
        input.systemId,
        input.spec,
        `Security journey ${input.spec.id} violation`,
        {
          journeyId: input.spec.id,
          kind: input.spec.kind,
          denied: input.denied,
          sideEffectCount: input.sideEffects.length,
          violated: input.violated,
        },
        laneEligibility,
      ),
    );
  }

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

function evaluateJourney(
  denied: boolean,
  sideEffects: SecuritySideEffect[],
): string[] {
  const violated: string[] = [];
  if (!denied) {
    violated.push("FEAT-060-access-not-denied");
  }
  if (sideEffects.length > 0) {
    violated.push("FEAT-061-denied-with-side-effect");
  }
  return violated;
}

export async function runNegativeSecurityJourney(
  system: SecurityBoundarySystem,
  spec: NegativeSecurityJourneySpec,
  runId = "security-journey-run",
): Promise<NegativeSecurityJourneyResult> {
  validateNegativeSecurityJourneySpec(spec);

  if (system.authFixtureHealthy !== undefined && !system.authFixtureHealthy()) {
    const coverageGap: SecurityCoverageGap = { reason: "auth-fixture-failure" };
    const classified = await classifySecurityJourney({
      systemId: system.id,
      spec,
      denied: false,
      sideEffects: [],
      violated: [],
      coverageGap,
      runId,
    });

    return {
      id: spec.id,
      kind: spec.kind,
      status: spec.status,
      holds: false,
      denied: false,
      sideEffects: [],
      response: { status: 0, body: {}, denied: false, timingClass: "fast" },
      classified,
      laneEligibility: "advisory",
      coverageGap,
    };
  }

  system.reset?.();
  const response = await system.handle(spec.request);
  const sideEffects = cloneSideEffects(system.snapshotSideEffects());
  const denied = response.denied;
  const violated = evaluateJourney(denied, sideEffects);
  const holds = violated.length === 0;
  const laneEligibility =
    spec.status === "confirmed" && violated.length > 0 ? "blocking-eligible" : "advisory";

  const classified = await classifySecurityJourney({
    systemId: system.id,
    spec,
    denied,
    sideEffects,
    violated,
    runId,
  });

  return {
    id: spec.id,
    kind: spec.kind,
    status: spec.status,
    holds,
    denied,
    sideEffects,
    response,
    classified,
    laneEligibility,
    ...(violated.length > 0 ? { violated } : {}),
  };
}
