import type { NonBrowserExecutionContext } from "../../../../schema/src/records/context.js";
import { featureAvailability } from "../../../../core/src/sdk/capability.js";
import type {
  Detector,
  DetectorContext,
  DetectorOutcome,
} from "../../../../core/src/sdk/detector.js";
import { buildBlockingEvidence, type BoundedBlockingEvidence } from "./evidence.js";
import {
  BOUNDED_CAPABILITY,
  detectUni050Violations,
  detectUni052Violations,
  detectUni053Violations,
  detectUni054Violations,
  isRuleExcepted,
  parseBoundedFixture,
  sanitizeForEvidence,
  type BoundedFixture,
  type BoundedViolation,
} from "./types.js";

const DETECTOR_VERSION = "1.0.0";

function capabilityUnavailable(context: DetectorContext): boolean {
  return featureAvailability(context.capabilities, BOUNDED_CAPABILITY) !== "available";
}

function makeExecutionContext(seed: string): NonBrowserExecutionContext {
  return {
    kind: "package" as const,
    surfaceId: "bounded-surface",
    adapterId: "bounded-surface",
    environment: {},
    seed,
  };
}

function violationsToFacts(violations: BoundedViolation[]) {
  return violations.map((violation) => ({
    scenarioId: violation.scenarioId,
    fact: violation.fact,
    kind: violation.kind,
  }));
}

function buildOutcome(
  detectorId: string,
  fixture: BoundedFixture,
  executionContext: NonBrowserExecutionContext,
  summary: string,
  evidence: BoundedBlockingEvidence,
  violation: unknown,
  laneEligibility: "blocking" | "blocking-eligible" = "blocking",
): DetectorOutcome<BoundedBlockingEvidence> {
  return {
    detector: { id: detectorId, version: DETECTOR_VERSION },
    class: "bounded-violation",
    severity: "high",
    target: {
      kind: "bounded-fixture",
      canonical: fixture.fixtureId,
    },
    context: executionContext,
    summary,
    evidence: [{ truthSource: "universal", payload: evidence }],
    artifacts: [],
    laneEligibility,
    scope: {
      id: `${detectorId}:${fixture.fixtureId}`,
      detectorId,
      surfaceId: executionContext.surfaceId,
    },
    violation,
  };
}

function evaluateViolations(
  detectorId: string,
  rule: string,
  fixture: BoundedFixture,
  context: DetectorContext,
  violations: BoundedViolation[],
  summary: string,
  laneEligibility: "blocking" | "blocking-eligible" = "blocking",
): DetectorOutcome<BoundedBlockingEvidence>[] {
  if (violations.length === 0) {
    return [];
  }

  const executionContext = makeExecutionContext(context.seed);
  const evidence = buildBlockingEvidence({
    rule,
    positiveWitness: {
      description: `${rule} boundedness evaluated against fixture scenarios`,
      fixtureId: fixture.fixtureId,
      boundedVersion: fixture.boundedVersion,
    },
    negativeWitness: {
      description: `${rule} boundedness violation observed`,
      kind: violations[0]?.kind ?? `${rule.toLowerCase()}-violation`,
      details: sanitizeForEvidence(violations),
    },
    minimalContradictoryFacts: violationsToFacts(violations),
    seed: context.seed,
    surfaceId: executionContext.surfaceId,
    detectorId,
  });

  return [
    buildOutcome(
      detectorId,
      fixture,
      executionContext,
      summary,
      evidence,
      violations,
      laneEligibility,
    ),
  ];
}

function evaluateDetectorViolations(
  detectorId: string,
  rule: string,
  fixture: BoundedFixture,
  context: DetectorContext,
  detect: (scenario: BoundedFixture["scenarios"][number]) => BoundedViolation[],
  summary: string,
): DetectorOutcome<BoundedBlockingEvidence>[] {
  const violations = fixture.scenarios.flatMap((scenario) => detect(scenario));
  return evaluateViolations(
    detectorId,
    rule,
    fixture,
    context,
    violations,
    summary,
    "blocking",
  );
}

export const uni050ConsumptionLimitDetector: Detector<unknown, BoundedBlockingEvidence> = {
  id: "uni-050-consumption-limit",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseBoundedFixture(input);
    if (isRuleExcepted(fixture, "UNI-050")) {
      return Promise.resolve([]);
    }

    return Promise.resolve(
      evaluateDetectorViolations(
        "uni-050-consumption-limit",
        "UNI-050",
        fixture,
        context,
        detectUni050Violations,
        "Untrusted input was limited after buffering instead of during consumption",
      ),
    );
  },
};

export const uni052WaitRetryDetector: Detector<unknown, BoundedBlockingEvidence> = {
  id: "uni-052-wait-retry",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseBoundedFixture(input);
    if (isRuleExcepted(fixture, "UNI-052")) {
      return Promise.resolve([]);
    }

    return Promise.resolve(
      evaluateDetectorViolations(
        "uni-052-wait-retry",
        "UNI-052",
        fixture,
        context,
        detectUni052Violations,
        "Wait or retry loop lacked bounded progress, cancellation, or observable terminal failure",
      ),
    );
  },
};

export const uni053ChildProcessDetector: Detector<unknown, BoundedBlockingEvidence> = {
  id: "uni-053-child-process",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseBoundedFixture(input);
    if (isRuleExcepted(fixture, "UNI-053")) {
      return Promise.resolve([]);
    }

    return Promise.resolve(
      evaluateDetectorViolations(
        "uni-053-child-process",
        "UNI-053",
        fixture,
        context,
        detectUni053Violations,
        "Child process inherited undeclared descriptors, environment, or process-group membership",
      ),
    );
  },
};

export const uni054BudgetThresholdDetector: Detector<unknown, BoundedBlockingEvidence> = {
  id: "uni-054-budget-threshold",
  version: DETECTOR_VERSION,
  defaultLane: "blocking-eligible",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseBoundedFixture(input);
    if (isRuleExcepted(fixture, "UNI-054")) {
      return Promise.resolve([]);
    }

    const violations = fixture.scenarios.flatMap((scenario) => detectUni054Violations(scenario));
    if (violations.length === 0) {
      return Promise.resolve([]);
    }

    const laneEligibility = violations.some(
      (violation) => violation.laneEligibility === "blocking",
    )
      ? "blocking"
      : "blocking-eligible";

    return Promise.resolve(
      evaluateViolations(
        "uni-054-budget-threshold",
        "UNI-054",
        fixture,
        context,
        violations,
        laneEligibility === "blocking"
          ? "Unbounded growth, leaked ownership, or integer overflow observed"
          : "Budget or limit threshold exceeded without confirmed environment contract",
        laneEligibility,
      ),
    );
  },
};
