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,
  factsFromVerdict,
  type ConsistencyBlockingEvidence,
} from "./evidence.js";
import { evaluateRelation, type RelationGraph } from "../../graph.js";
import {
  CONSISTENCY_CAPABILITY,
  isRuleExcepted,
  isSuccessStatus,
  parseConsistencyFixture,
  type ConsistencyFixture,
} from "./types.js";

const DETECTOR_VERSION = "1.0.0";

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

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

function graphRelationsOfKind(
  graph: RelationGraph,
  kind: "same-semantic" | "derived-value",
): Array<[string, RelationGraph["relations"][string]]> {
  return Object.entries(graph.relations).filter(([, relation]) => relation.kind === kind);
}

function evaluateGraphRelations(
  detectorId: string,
  rule: string,
  fixture: ConsistencyFixture,
  graph: RelationGraph,
  kind: "same-semantic" | "derived-value",
  context: DetectorContext,
  executionContext: NonBrowserExecutionContext,
  summaryForRelation: (relationId: string) => string,
): DetectorOutcome<ConsistencyBlockingEvidence>[] {
  const outcomes: DetectorOutcome<ConsistencyBlockingEvidence>[] = [];

  for (const [relationId] of graphRelationsOfKind(graph, kind)) {
    const verdict = evaluateRelation(graph, relationId);
    if (verdict.status !== "contradiction") {
      continue;
    }

    const evidence = buildBlockingEvidence({
      rule,
      positiveWitness: {
        description: `${kind} relation evaluated without selecting intended side`,
        fixtureId: fixture.fixtureId,
        relationId,
      },
      negativeWitness: {
        description: `${kind} relation contradiction`,
        kind: `${kind}-contradiction`,
        verdict,
      },
      minimalContradictoryFacts: factsFromVerdict(verdict),
      seed: context.seed,
      surfaceId: executionContext.surfaceId,
      detectorId,
    });

    outcomes.push(
      buildOutcome(
        detectorId,
        fixture,
        executionContext,
        summaryForRelation(relationId),
        evidence,
        { relationId, verdict },
      ),
    );
  }

  return outcomes;
}

export const uni020SameSemanticDetector: Detector<unknown, ConsistencyBlockingEvidence> = {
  id: "uni-020-same-semantic",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseConsistencyFixture(input);
    if (isRuleExcepted(fixture, "UNI-020") || fixture.graph === undefined) {
      return Promise.resolve([]);
    }

    const executionContext: NonBrowserExecutionContext = {
      kind: "package" as const,
      surfaceId: "consistency-surface",
      adapterId: "consistency-surface",
      environment: {},
      seed: context.seed,
    };

    return Promise.resolve(
      evaluateGraphRelations(
        "uni-020-same-semantic",
        "UNI-020",
        fixture,
        fixture.graph,
        "same-semantic",
        context,
        executionContext,
        (relationId) =>
          `Same-semantic representations disagree after normalization (${relationId})`,
      ),
    );
  },
};

export const uni021DerivedValueDetector: Detector<unknown, ConsistencyBlockingEvidence> = {
  id: "uni-021-derived-value",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseConsistencyFixture(input);
    if (isRuleExcepted(fixture, "UNI-021") || fixture.graph === undefined) {
      return Promise.resolve([]);
    }

    const executionContext: NonBrowserExecutionContext = {
      kind: "package" as const,
      surfaceId: "consistency-surface",
      adapterId: "consistency-surface",
      environment: {},
      seed: context.seed,
    };

    return Promise.resolve(
      evaluateGraphRelations(
        "uni-021-derived-value",
        "UNI-021",
        fixture,
        fixture.graph,
        "derived-value",
        context,
        executionContext,
        (relationId) => `Derived-value relation fails closure (${relationId})`,
      ),
    );
  },
};

export const uni022TerminalStatusDetector: Detector<unknown, ConsistencyBlockingEvidence> = {
  id: "uni-022-terminal-status",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseConsistencyFixture(input);
    if (isRuleExcepted(fixture, "UNI-022") || fixture.terminal === undefined) {
      return Promise.resolve([]);
    }

    const terminal = fixture.terminal;
    if (!isSuccessStatus(terminal.claimedStatus)) {
      return Promise.resolve([]);
    }

    const outstanding = terminal.obligations.filter(
      (obligation) => obligation.mandatory && !obligation.completed,
    );
    if (outstanding.length === 0) {
      return Promise.resolve([]);
    }

    const executionContext: NonBrowserExecutionContext = {
      kind: "package" as const,
      surfaceId: "consistency-surface",
      adapterId: "consistency-surface",
      environment: {},
      seed: context.seed,
    };

    const minimalContradictoryFacts = outstanding.map((obligation) => ({
      fact: `outstanding ${obligation.kind}: ${obligation.id}`,
      kind: obligation.kind,
    }));

    const evidence = buildBlockingEvidence({
      rule: "UNI-022",
      positiveWitness: {
        description: "terminal status evaluated against completion obligations",
        fixtureId: fixture.fixtureId,
      },
      negativeWitness: {
        description: "terminal success claimed with outstanding mandatory obligations",
        kind: "premature-terminal-success",
        details: { claimedStatus: terminal.claimedStatus, outstanding },
      },
      minimalContradictoryFacts,
      seed: context.seed,
      surfaceId: executionContext.surfaceId,
      detectorId: "uni-022-terminal-status",
    });

    return Promise.resolve([
      buildOutcome(
        "uni-022-terminal-status",
        fixture,
        executionContext,
        "Terminal success contradicts outstanding mandatory completion obligation",
        evidence,
        { claimedStatus: terminal.claimedStatus, outstanding },
      ),
    ]);
  },
};

export const uni023DurableOutcomeDetector: Detector<unknown, ConsistencyBlockingEvidence> = {
  id: "uni-023-durable-outcome",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseConsistencyFixture(input);
    if (isRuleExcepted(fixture, "UNI-023") || fixture.terminal === undefined) {
      return Promise.resolve([]);
    }

    const terminal = fixture.terminal;
    if (!isSuccessStatus(terminal.claimedStatus)) {
      return Promise.resolve([]);
    }

    const durableDisagrees =
      terminal.durableOutcome !== undefined &&
      terminal.durableOutcome.status.trim().toLowerCase() !== "success";
    const sideEffectMissing =
      terminal.observableSideEffect !== undefined &&
      !terminal.observableSideEffect.observed;

    if (!durableDisagrees && !sideEffectMissing) {
      return Promise.resolve([]);
    }

    const executionContext: NonBrowserExecutionContext = {
      kind: "package" as const,
      surfaceId: "consistency-surface",
      adapterId: "consistency-surface",
      environment: {},
      seed: context.seed,
    };

    const minimalContradictoryFacts: Array<{ fact: string; kind?: string }> = [];
    const durableOutcome = terminal.durableOutcome;
    const observableSideEffect = terminal.observableSideEffect;
    if (durableDisagrees && durableOutcome !== undefined) {
      minimalContradictoryFacts.push({
        fact: `durable outcome status is ${durableOutcome.status}`,
        kind: "durable-outcome",
      });
    }
    if (sideEffectMissing && observableSideEffect !== undefined) {
      minimalContradictoryFacts.push({
        fact: `observable side effect ${observableSideEffect.kind} not observed`,
        kind: "observable-side-effect",
      });
    }

    const evidence = buildBlockingEvidence({
      rule: "UNI-023",
      positiveWitness: {
        description: "claimed success compared to durable outcome and side effects",
        fixtureId: fixture.fixtureId,
      },
      negativeWitness: {
        description: "claimed success without durable agreement or observable completion",
        kind: "durable-outcome-disagreement",
        details: {
          claimedStatus: terminal.claimedStatus,
          durableOutcome: terminal.durableOutcome,
          observableSideEffect: terminal.observableSideEffect,
        },
      },
      minimalContradictoryFacts,
      seed: context.seed,
      surfaceId: executionContext.surfaceId,
      detectorId: "uni-023-durable-outcome",
    });

    return Promise.resolve([
      buildOutcome(
        "uni-023-durable-outcome",
        fixture,
        executionContext,
        "Claimed success disagrees with durable outcome or observable side effect",
        evidence,
        {
          claimedStatus: terminal.claimedStatus,
          durableOutcome: terminal.durableOutcome,
          observableSideEffect: terminal.observableSideEffect,
        },
      ),
    ]);
  },
};

export const uni024ContradictionWithoutWinnerDetector: Detector<
  unknown,
  ConsistencyBlockingEvidence
> = {
  id: "uni-024-contradiction-without-winner",
  version: DETECTOR_VERSION,
  defaultLane: "blocking",
  evaluate(input, context) {
    if (capabilityUnavailable(context)) {
      return Promise.resolve([]);
    }

    const fixture = parseConsistencyFixture(input);
    if (isRuleExcepted(fixture, "UNI-024") || fixture.graph === undefined) {
      return Promise.resolve([]);
    }

    const executionContext: NonBrowserExecutionContext = {
      kind: "package" as const,
      surfaceId: "consistency-surface",
      adapterId: "consistency-surface",
      environment: {},
      seed: context.seed,
    };

    const outcomes: DetectorOutcome<ConsistencyBlockingEvidence>[] = [];

    for (const kind of ["same-semantic", "derived-value"] as const) {
      for (const [relationId] of graphRelationsOfKind(fixture.graph, kind)) {
        const verdict = evaluateRelation(fixture.graph, relationId);
        if (verdict.status !== "contradiction") {
          continue;
        }

        const evidence = buildBlockingEvidence({
          rule: "UNI-024",
          positiveWitness: {
            description: "relation contradiction proven without intended-side selection",
            fixtureId: fixture.fixtureId,
            relationId,
          },
          negativeWitness: {
            description: "contradiction without winner",
            kind: "contradiction-without-winner",
            verdict,
          },
          minimalContradictoryFacts: factsFromVerdict(verdict),
          noWinner: true,
          fixPacket: {
            statesContradiction: true,
            guessesWinner: false,
          },
          seed: context.seed,
          surfaceId: executionContext.surfaceId,
          detectorId: "uni-024-contradiction-without-winner",
        });

        outcomes.push(
          buildOutcome(
            "uni-024-contradiction-without-winner",
            fixture,
            executionContext,
            `Representation contradiction proven with no side selected (${relationId})`,
            evidence,
            { relationId, verdict, noWinner: true },
          ),
        );
      }
    }

    return Promise.resolve(outcomes);
  },
};
