import { resolveToken } from "./gh/auth-and-safety";
import { githubRequest, isGitHubError } from "./gh/client";

export interface PacDeliveryTimingBinding {
  target: Record<string, unknown>;
  consumer: {
    id: string;
    node: string;
    lane: string;
    repository: string;
    branch: string;
    pipeline: string;
  };
  policy: PacDeliveryTimingPolicy;
}

export interface PacDeliveryTimingPolicy {
  endToEndBudgetSeconds: number;
  configPath: string;
}

export interface PacDeliveryBudgetObservationInput {
  policy: PacDeliveryTimingPolicy;
  targetId: string;
  consumerId: string;
  pipelineRunId: string | null;
  endToEndSeconds: number | null;
  complete: boolean;
}

interface PacDeliveryTimingObservers {
  history: () => Promise<Record<string, unknown>>;
  status: () => Promise<Record<string, unknown>>;
}

function record(value: unknown): Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function records(value: unknown): Record<string, unknown>[] {
  return Array.isArray(value) ? value.map(record) : [];
}

function durationBetween(start: unknown, end: unknown): number | null {
  if (typeof start !== "string" || typeof end !== "string") return null;
  const a = Date.parse(start);
  const b = Date.parse(end);
  if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
  return Math.max(0, Math.round((b - a) / 1000));
}

export function pacDeliveryTimingWindow(row: Record<string, unknown>): Record<string, unknown> {
  const deliveryStart = typeof row.triggeredAt === "string" ? row.triggeredAt : null;
  const pipelineStart = typeof row.startTime === "string" ? row.startTime : null;
  const pipelineCompletion = typeof row.completionTime === "string" ? row.completionTime : null;
  return {
    deliveryStart,
    deliveryStartSource: deliveryStart === null ? null : "pac-webhook-admission",
    pipelineStart,
    pipelineCompletion,
    triggerWaitSeconds: durationBetween(deliveryStart, pipelineStart),
    pipelineDurationSeconds: typeof row.durationSeconds === "number"
      ? row.durationSeconds
      : durationBetween(pipelineStart, pipelineCompletion),
    endToEndSeconds: durationBetween(deliveryStart, pipelineCompletion),
  };
}

function summarizeTaskRuns(value: unknown): Record<string, unknown> {
  const taskRuns = record(value);
  const details = records(taskRuns.details).map((item) => ({
    name: item.name ?? null,
    status: item.status ?? null,
    reason: item.reason ?? null,
    durationSeconds: item.durationSeconds ?? null,
  }));
  const timed = details.filter((item) => typeof item.durationSeconds === "number");
  const longest = timed.reduce<Record<string, unknown> | null>((selected, item) => (
    selected === null || Number(item.durationSeconds) > Number(selected.durationSeconds) ? item : selected
  ), null);
  return {
    count: typeof taskRuns.count === "number" ? taskRuns.count : details.length,
    stages: details,
    longest,
    valuesPrinted: false,
  };
}

export function observePacDeliveryBudget(input: PacDeliveryBudgetObservationInput): Record<string, unknown> {
  const evidenceState = input.complete && input.endToEndSeconds !== null ? "complete" : "partial";
  const overBudget = evidenceState === "complete" && (input.endToEndSeconds as number) > input.policy.endToEndBudgetSeconds;
  const warning = overBudget ? {
    code: "pac-manual-delivery-over-budget",
    severity: "warning",
    blocking: false,
    mutation: false,
    object: { kind: "consumer", id: input.consumerId },
    configPath: input.policy.configPath,
    metric: "end-to-end-seconds",
    observedSeconds: input.endToEndSeconds,
    budgetSeconds: input.policy.endToEndBudgetSeconds,
    hint: {
      summary: "优先检查并优化 env reuse；对照 env identity、依赖缓存、BuildKit/cache 和 stage timing 定位慢阶段。",
      status: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${input.targetId} --consumer ${input.consumerId} --json`,
      history: input.pipelineRunId === null
        ? `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${input.targetId} --consumer ${input.consumerId} --limit 10 --json`
        : `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${input.targetId} --consumer ${input.consumerId} --id ${input.pipelineRunId} --full`,
      focus: ["env-identity", "dependency-cache", "buildkit-cache", "stage-timing"],
      mutation: false,
      valuesPrinted: false,
    },
    valuesPrinted: false,
  } : null;
  return {
    state: evidenceState,
    metric: "end-to-end-seconds",
    observedSeconds: input.endToEndSeconds,
    budgetSeconds: input.policy.endToEndBudgetSeconds,
    overBudget: evidenceState === "complete" ? overBudget : null,
    configPath: input.policy.configPath,
    warning,
    blocking: false,
    mutation: false,
    valuesPrinted: false,
  };
}

export function observePacStatusDeliveryBudget(input: {
  policy: PacDeliveryTimingPolicy;
  targetId: string;
  consumerId: string;
  summary: Record<string, unknown> | null;
}): Record<string, unknown> {
  const latest = record(input.summary?.latestPipelineRun);
  const observedEndToEnd = typeof latest.endToEndSeconds === "number" ? latest.endToEndSeconds : null;
  return {
    ...observePacDeliveryBudget({
      policy: input.policy,
      targetId: input.targetId,
      consumerId: input.consumerId,
      pipelineRunId: typeof latest.name === "string" ? latest.name : null,
      endToEndSeconds: observedEndToEnd,
      complete: input.summary?.ready === true && observedEndToEnd !== null,
    }),
    evidenceSource: "status-summary",
    pipelineDurationSeconds: typeof latest.durationSeconds === "number" ? latest.durationSeconds : null,
    exactCommand: `bun scripts/cli.ts platform-infra pipelines-as-code delivery-timing --target ${input.targetId} --consumer ${input.consumerId} --json`,
  };
}

export function observePacHistoryDeliveryBudget(input: {
  policy: PacDeliveryTimingPolicy;
  targetId: string;
  consumerIds: readonly string[];
  defaultConsumerId: string;
  rows: readonly Record<string, unknown>[];
}): Record<string, unknown> {
  const observations = input.rows.map((row) => {
    const consumerId = typeof row.consumer === "string" ? row.consumer : input.consumerIds[0] ?? input.defaultConsumerId;
    const observedEndToEnd = typeof row.endToEndSeconds === "number" ? row.endToEndSeconds : null;
    return {
      ...observePacDeliveryBudget({
        policy: input.policy,
        targetId: input.targetId,
        consumerId,
        pipelineRunId: typeof row.id === "string" ? row.id : typeof row.pipelineRun === "string" ? row.pipelineRun : null,
        endToEndSeconds: observedEndToEnd,
        complete: row.deliveryComplete === true && observedEndToEnd !== null,
      }),
      consumer: consumerId,
      pipelineRun: row.id ?? row.pipelineRun ?? null,
      evidenceSource: "history-summary",
      pipelineDurationSeconds: typeof row.durationSeconds === "number" ? row.durationSeconds : null,
    };
  });
  return {
    state: observations.length > 0 && observations.every((item) => item.state === "complete") ? "complete" : "partial",
    policy: input.policy,
    observations,
    exactCommand: input.consumerIds.length === 1
      ? `bun scripts/cli.ts platform-infra pipelines-as-code delivery-timing --target ${input.targetId} --consumer ${input.consumerIds[0]} --json`
      : null,
    blocking: false,
    mutation: false,
    valuesPrinted: false,
  };
}

export async function observePacDeliveryTiming(
  binding: PacDeliveryTimingBinding,
  observers: PacDeliveryTimingObservers,
): Promise<Record<string, unknown>> {
  const [sourceOwner, sourceRepo] = binding.consumer.repository.split("/");
  if (!sourceOwner || !sourceRepo) throw new Error(`invalid owning YAML source repository ${binding.consumer.repository}`);
  const [historyResult, statusResult] = await Promise.all([observers.history(), observers.status()]);
  const row = records(historyResult.rows)[0] ?? {};
  const statusSummary = record(statusResult.summary);
  const latest = record(statusSummary.latestPipelineRun);
  const commit = typeof row.commit === "string" ? row.commit : typeof latest.sourceCommit === "string" ? latest.sourceCommit : null;
  const evidenceGaps: string[] = [];
  const budgetEvidenceGaps: string[] = [];
  let pullRequest: Record<string, unknown> | null = null;
  let githubEvidence: Record<string, unknown> = { repository: binding.consumer.repository, source: "github-commit-pulls", mutation: false };
  if (commit === null) {
    evidenceGaps.push("source-commit-missing");
    budgetEvidenceGaps.push("source-commit-missing");
  } else {
    const tokenInfo = resolveToken(binding.consumer.repository, false);
    if (tokenInfo.token === null) {
      evidenceGaps.push("github-token-unavailable");
      githubEvidence = { ...githubEvidence, ok: false, token: tokenInfo.probe };
    } else {
      const response = await githubRequest<unknown>(tokenInfo.token, "GET", `/repos/${sourceOwner}/${sourceRepo}/commits/${commit}/pulls`);
      if (isGitHubError(response)) {
        evidenceGaps.push("github-pr-lookup-failed");
        githubEvidence = { ...githubEvidence, ok: false, error: response };
      } else {
        const prs = Array.isArray(response) ? response : [];
        const merged = prs.find((item) => record(item).merged_at !== null && record(item).merged_at !== undefined) ?? prs[0];
        if (merged === undefined) {
          evidenceGaps.push("github-pr-not-found");
        } else {
          const item = record(merged);
          pullRequest = { number: item.number ?? null, title: item.title ?? null, url: item.html_url ?? null, mergedAt: item.merged_at ?? null, mergeCommit: commit };
          if (typeof item.merged_at !== "string") evidenceGaps.push("github-mergedAt-missing");
        }
        githubEvidence = { ...githubEvidence, ok: true, count: prs.length, token: tokenInfo.probe };
      }
    }
  }
  const mergedAt = pullRequest?.mergedAt ?? null;
  const timing = pacDeliveryTimingWindow(row);
  const deliveryStart = timing.deliveryStart;
  const startTime = timing.pipelineStart;
  const completionTime = timing.pipelineCompletion;
  const argo = record(statusSummary.argo);
  const runtime = record(statusSummary.runtime);
  const provenance = record(statusSummary.provenance);
  if (mergedAt === null) evidenceGaps.push("mergedAt-missing");
  if (deliveryStart === null) budgetEvidenceGaps.push("manual-webhook-admission-missing");
  if (startTime === null || completionTime === null) budgetEvidenceGaps.push("pipeline-timestamps-missing");
  if (runtime.readyReplicas === undefined) budgetEvidenceGaps.push("runtime-health-missing");
  const replicas = typeof runtime.replicas === "number" ? runtime.replicas : null;
  const readyReplicas = typeof runtime.readyReplicas === "number" ? runtime.readyReplicas : null;
  if (replicas !== null && (replicas <= 0 || readyReplicas === null || readyReplicas < replicas)) {
    budgetEvidenceGaps.push("runtime-minimal-readiness-not-ready");
  }
  evidenceGaps.push(...budgetEvidenceGaps);
  const state = budgetEvidenceGaps.length === 0 ? "complete" : "partial";
  const endToEndSeconds = typeof timing.endToEndSeconds === "number" ? timing.endToEndSeconds : null;
  const pipelineRunId = typeof row.id === "string"
    ? row.id
    : typeof latest.name === "string"
      ? latest.name
      : null;
  const deliveryBudget = observePacDeliveryBudget({
    policy: binding.policy,
    targetId: String(binding.target.id ?? binding.consumer.node),
    consumerId: binding.consumer.id,
    pipelineRunId,
    endToEndSeconds,
    complete: state === "complete",
  });
  const warning = record(deliveryBudget.warning);
  return {
    ok: commit !== null && startTime !== null && completionTime !== null,
    action: "platform-infra-pipelines-as-code-delivery-timing",
    mutation: false,
    state,
    target: binding.target,
    consumer: binding.consumer,
    source: { commit, pullRequest, github: githubEvidence },
    timing: { mergedAt, ...timing },
    deliveryBudget,
    warnings: Object.keys(warning).length === 0 ? [] : [warning],
    pipeline: {
      id: pipelineRunId,
      status: row.status ?? latest.status ?? null,
      taskRuns: summarizeTaskRuns(row.taskRuns ?? statusSummary.taskRuns ?? null),
    },
    runtime: { argo, health: runtime, provenance },
    evidenceGaps: [...new Set(evidenceGaps)],
    budgetEvidenceGaps: [...new Set(budgetEvidenceGaps)],
    valuesPrinted: false,
  };
}
