import { selectRequestDeliveryLineage } from "@overdeck/report-contract";
import type {
  ExecutionReportSection,
  LineageRecord,
  LineageReportSection,
  LineageStage,
  ReportCoverageGap,
  ReportCoverageStatus,
  RequestStoryEvidenceLink,
  RequestStoryV1,
} from "@overdeck/report-contract";

export interface LineageReportInput {
  execution: ExecutionReportSection;
  requestStories?: RequestStoryV1[];
}

function requestEvidence(requestId: string, href: string): LineageStage["evidence"] {
  return { source: "request-registry", recordId: requestId, href };
}

function safeOwnerHref(value: string | undefined, fallback: string): string {
  return value?.startsWith("/") && !value.startsWith("//") && !value.includes("\\") ? value : fallback;
}

function linkedEvidence(link: RequestStoryEvidenceLink, fallback: string): LineageStage["evidence"] {
  return {
    source: "request-evidence",
    recordId: link.targetId,
    href: safeOwnerHref(link.ownerUrl, fallback),
  };
}

function latest(story: RequestStoryV1 | undefined, kind: RequestStoryEvidenceLink["kind"]): RequestStoryEvidenceLink | undefined {
  return story?.links.filter((link) => link.kind === kind)
    .sort((left, right) => right.occurredAt.localeCompare(left.occurredAt))[0];
}

function missingStage(kind: LineageStage["kind"], label: string, detail: string): LineageStage {
  return { kind, label, status: "missing", detail };
}

function failedMetadata(value: unknown): boolean {
  return typeof value === "string" && ["fail", "failed", "error"].includes(value.trim().toLocaleLowerCase());
}

function buildLinkedStage(
  story: RequestStoryV1 | undefined,
  kind: RequestStoryEvidenceLink["kind"],
  stageKind: LineageStage["kind"],
  label: string,
  missingLabel: string,
  missingDetail: string,
  detail: (link: RequestStoryEvidenceLink) => string,
  fallbackHref: string,
  statusField?: string,
): { stage: LineageStage; gap?: string } {
  const link = latest(story, kind);
  if (!link) return { stage: missingStage(stageKind, missingLabel, missingDetail), gap: missingDetail };
  return {
    stage: {
      kind: stageKind,
      label,
      status: statusField && failedMetadata(link.metadata[statusField]) ? "failed" : "recorded",
      detail: detail(link),
      recordedAt: link.occurredAt,
      evidence: linkedEvidence(link, fallbackHref),
    },
  };
}

function buildLineage(
  request: ExecutionReportSection["requests"][number],
  story: RequestStoryV1 | undefined,
): LineageRecord {
  const gaps: ReportCoverageGap[] = [];
  const stages: LineageStage[] = [{
    kind: "request",
    label: "Request recorded",
    status: "recorded",
    detail: `Asked in ${request.project}.`,
    recordedAt: request.askedAt,
    evidence: requestEvidence(request.id, request.requestHref),
  }];
  const add = (result: { stage: LineageStage; gap?: string }, sourceId = "request-evidence-links") => {
    stages.push(result.stage);
    if (result.gap) gaps.push({ domain: "lineage", sourceId, reason: `${request.title}: ${result.gap}` });
  };

  add(buildLinkedStage(
    story, "factory_run", "run", "Run linked", "Run not linked",
    "No factory run was explicitly linked to this request.",
    (link) => `Exact work run ${link.targetId} is linked.`, request.requestHref, "status",
  ));
  add(buildLinkedStage(
    story, "gate", "gate", "Checks recorded", "Checks not recorded",
    "No exact check result is recorded for this request.",
    (link) => `Recorded check ${link.targetId}.`, request.requestHref, "verdict",
  ));
  add(buildLinkedStage(
    story, "change", "change", "Change recorded", "Change not recorded",
    "No exact change record is recorded for this request.",
    (link) => `Recorded change ${link.targetId}.`, request.requestHref,
  ));

  const { branch, submission, commit, deployment, proof } = selectRequestDeliveryLineage(story?.links ?? []);
  if (branch && submission && commit) {
    stages.push({
      kind: "land",
      label: "Commit landed",
      status: failedMetadata(commit.metadata.gateVerdict) ? "failed" : "recorded",
      detail: `${branch.targetId} was submitted and landed as ${commit.targetId}.`,
      recordedAt: commit.occurredAt,
      evidence: linkedEvidence(commit, request.requestHref),
    });
  } else {
    const missing = [!branch ? "branch" : null, !submission ? "landing submission" : null, !commit ? "landed commit" : null]
      .filter((value): value is string => value !== null);
    const detail = `Exact ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not recorded.`;
    stages.push(missingStage("land", "Landing incomplete", detail));
    gaps.push({ domain: "lineage", sourceId: "request-evidence-links", reason: `${request.title}: ${detail}` });
  }

  const deploymentReleaseMatches = !commit || deployment?.metadata.release === commit.targetId;
  if (deployment && deploymentReleaseMatches) {
    const release = typeof deployment.metadata.release === "string" ? deployment.metadata.release : "release not recorded";
    const readiness = typeof deployment.metadata.readiness === "string" ? deployment.metadata.readiness : "readiness not recorded";
    stages.push({
      kind: "deploy",
      label: "Deployment recorded",
      status: failedMetadata(deployment.metadata.status) ? "failed" : "recorded",
      detail: `Release ${release}; readiness ${readiness}.`,
      recordedAt: deployment.occurredAt,
      evidence: linkedEvidence(deployment, request.requestHref),
    });
  } else {
    const detail = deployment
      ? "The latest deployment record belongs to a different landed commit."
      : "No exact deployment and readiness result is recorded for this request.";
    stages.push(missingStage("deploy", "Deployment not recorded", detail));
    gaps.push({ domain: "lineage", sourceId: "request-evidence-links", reason: `${request.title}: ${detail}` });
  }

  const proofReleaseMatches = !commit || proof?.metadata.release === commit.targetId;
  if (proof && proofReleaseMatches) {
    stages.push({
      kind: "proof",
      label: "Installed proof recorded",
      status: failedMetadata(proof.metadata.result) ? "failed" : "recorded",
      detail: `Installed proof ${proof.targetId} is recorded.`,
      recordedAt: proof.occurredAt,
      evidence: linkedEvidence(proof, request.requestHref),
    });
  } else {
    const detail = proof
      ? "The latest installed proof belongs to a different landed commit."
      : "No exact installed owner proof is recorded for this request.";
    stages.push(missingStage("proof", "Installed proof missing", detail));
    gaps.push({ domain: "lineage", sourceId: "request-evidence-links", reason: `${request.title}: ${detail}` });
  }

  return {
    requestId: request.id,
    title: request.title,
    project: request.project,
    complete: stages.every((stage) => stage.status === "recorded"),
    stages,
    gaps,
  };
}

export function buildLineageReportSection(input: LineageReportInput): LineageReportSection {
  const requestMetric = input.execution.metrics.find((metric) => metric.id === "requests");
  const requestAuthorityAvailable = requestMetric !== undefined && requestMetric.value !== null;
  const storyAuthorityAvailable = input.requestStories !== undefined;
  const storyById = new Map((input.requestStories ?? []).map((story) => [story.requestId, story]));
  const lineages = input.execution.requests.map((request) => buildLineage(request, storyById.get(request.id)));
  const gaps = lineages.flatMap((lineage) => lineage.gaps);
  if (!requestAuthorityAvailable) gaps.unshift({ domain: "lineage", sourceId: "request-registry", reason: "Request records are unavailable for delivery lineage." });
  if (!storyAuthorityAvailable) gaps.unshift({ domain: "lineage", sourceId: "request-evidence-links", reason: "Exact request evidence is unavailable for delivery lineage." });
  let status: ReportCoverageStatus = !requestAuthorityAvailable && !storyAuthorityAvailable
    ? "unavailable"
    : !requestAuthorityAvailable || !storyAuthorityAvailable || gaps.length > 0
      ? "partial"
      : "complete";
  if (lineages.length > 0 && lineages.every((lineage) => !lineage.complete) && !requestAuthorityAvailable) status = "unavailable";
  return {
    kind: "lineage",
    status,
    title: "Delivery lineage",
    lineages,
    gaps,
    truncated: input.execution.truncated,
    errors: [],
  };
}
