// SPEC: PJ2026-01060703 CI/CD branch follower draft-2026-07-03-p0-branch-follower.
// Responsibility: compact native Kubernetes status summaries for branch-follower closeout.
import { resolveAgentRunLaneTarget } from "./agentrun-lanes";
import type { FollowerSpec, NativeStatusSpec, NativeWorkloadSpec } from "./cicd-types";
import { hwlabRuntimeLaneSpecForNode } from "./hwlab-node-lanes";

export function pipelineRunSucceeded(pipelineRun: Record<string, unknown> | null): boolean | null {
  if (pipelineRun === null) return null;
  const conditions = Array.isArray(asOptionalRecord(pipelineRun.status)?.conditions) ? asOptionalRecord(pipelineRun.status)?.conditions : [];
  for (const condition of conditions as unknown[]) {
    const record = asOptionalRecord(condition);
    if (record?.type !== "Succeeded") continue;
    if (record.status === "True") return true;
    if (record.status === "False") return false;
    return null;
  }
  return null;
}

export function argoApplicationReady(application: Record<string, unknown> | null): boolean {
  if (application === null) return false;
  const status = asOptionalRecord(application.status);
  const sync = asOptionalRecord(status?.sync);
  const health = asOptionalRecord(status?.health);
  return sync?.status === "Synced" && health?.status === "Healthy";
}

export function runtimeWorkloadsReady(runtime: NativeStatusSpec["runtime"], workloads: Record<string, unknown>[]): boolean {
  if (runtime === null) return true;
  if (workloads.length < runtime.workloads.length) return false;
  return runtime.workloads.every((spec, index) => workloadReady(spec, workloads[index] ?? null));
}

export function runtimeTargetShaFromWorkloads(runtime: NativeStatusSpec["runtime"], workloads: Record<string, unknown>[]): string | null {
  if (runtime === null) return null;
  const commits: string[] = [];
  runtime.workloads.forEach((spec, index) => {
    const commit = workloadSourceCommit(spec, workloads[index] ?? null);
    if (commit !== null) commits.push(commit);
  });
  const unique = Array.from(new Set(commits));
  return unique.length === 1 ? unique[0] ?? null : null;
}

export function nativePipelineRunSummary(pipelineRun: Record<string, unknown> | null): Record<string, unknown> | null {
  if (pipelineRun === null) return null;
  const metadata = asOptionalRecord(pipelineRun.metadata);
  const status = asOptionalRecord(pipelineRun.status);
  const condition = latestCondition(status, "Succeeded");
  return {
    name: stringOrNull(metadata?.name),
    namespace: stringOrNull(metadata?.namespace),
    pipelineRefName: stringOrNull(asOptionalRecord(pipelineRun.spec)?.pipelineRef?.name),
    succeeded: pipelineRunSucceeded(pipelineRun),
    reason: stringOrNull(condition?.reason),
    startTime: stringOrNull(status?.startTime),
    completionTime: stringOrNull(status?.completionTime),
    durationSeconds: numberOrNull(status?.durationSeconds),
  };
}

export function nativeArgoSummary(application: Record<string, unknown> | null): Record<string, unknown> | null {
  if (application === null) return null;
  const metadata = asOptionalRecord(application.metadata);
  const status = asOptionalRecord(application.status);
  const sync = asOptionalRecord(status?.sync);
  const health = asOptionalRecord(status?.health);
  const operationState = asOptionalRecord(status?.operationState);
  const syncResultResources = Array.isArray(operationState?.syncResultResources) ? operationState.syncResultResources.slice(0, 5) : [];
  return {
    name: stringOrNull(metadata?.name),
    namespace: stringOrNull(metadata?.namespace),
    syncStatus: stringOrNull(sync?.status),
    healthStatus: stringOrNull(health?.status),
    healthMessage: stringOrNull(health?.message),
    revision: stringOrNull(sync?.revision),
    operationPhase: stringOrNull(operationState?.phase),
    operationMessage: stringOrNull(operationState?.message),
    operationStartedAt: stringOrNull(operationState?.startedAt),
    operationFinishedAt: stringOrNull(operationState?.finishedAt),
    operationDurationSeconds: numberOrNull(operationState?.durationSeconds),
    conditions: Array.isArray(status?.conditions) ? status.conditions.slice(0, 5) : [],
    nonReadyResources: Array.isArray(status?.nonReadyResources) ? status.nonReadyResources.slice(0, 5) : [],
    syncResultResources,
    ready: argoApplicationReady(application),
  };
}

export function nativeRuntimeSummary(runtime: NativeStatusSpec["runtime"], workloads: Record<string, unknown>[], expectedSha: string | null): Record<string, unknown> | null {
  if (runtime === null) return null;
  const targetSha = runtimeTargetShaFromWorkloads(runtime, workloads);
  return {
    namespace: runtime.namespace,
    ready: runtimeWorkloadsReady(runtime, workloads),
    targetSha,
    expectedSha,
    aligned: expectedSha !== null && targetSha !== null ? targetSha === expectedSha : null,
    workloads: runtime.workloads.map((spec, index) => {
      const workload = workloads[index] ?? null;
      const sourceCommit = workloadSourceCommit(spec, workload);
      return {
        kind: spec.kind,
        name: spec.name,
        ready: workloadReady(spec, workload),
        sourceCommit,
        aligned: expectedSha !== null && sourceCommit !== null ? sourceCommit === expectedSha : null,
      };
    }),
  };
}

export function nativeGitMirrorRequired(follower: FollowerSpec): boolean {
  return nativeGitMirrorGitopsBranch(follower) !== null
    && (follower.closeoutChecks.includes("gitMirrorPostFlush") || follower.closeoutChecks.includes("gitops"));
}

export function nativeGitMirrorReady(gitMirror: Record<string, unknown> | null): boolean {
  if (gitMirror === null) return false;
  if (gitMirror.ok === false) return false;
  if (gitMirror.sourceSnapshotReady === false) return false;
  if (gitMirror.pendingFlush === true) return false;
  if (gitMirror.githubInSync === false) return false;
  return true;
}

export function nativeGitMirrorSummary(gitMirror: Record<string, unknown> | null): Record<string, unknown> | null {
  if (gitMirror === null) return null;
  return {
    ok: gitMirror.ok === true,
    repository: stringOrNull(gitMirror.repository),
    sourceBranch: stringOrNull(gitMirror.sourceBranch),
    gitopsBranch: stringOrNull(gitMirror.gitopsBranch),
    localSource: stringOrNull(gitMirror.localSource),
    githubSource: stringOrNull(gitMirror.githubSource),
    sourceStageRef: stringOrNull(gitMirror.sourceStageRef),
    sourceSnapshotReady: gitMirror.sourceSnapshotReady === true,
    localGitops: stringOrNull(gitMirror.localGitops),
    githubGitops: stringOrNull(gitMirror.githubGitops),
    pendingFlush: gitMirror.pendingFlush === true,
    githubInSync: gitMirror.githubInSync === true,
    statusAuthority: stringOrNull(gitMirror.statusAuthority) ?? "k8s-git-mirror-cache",
  };
}

function nativeGitMirrorGitopsBranch(follower: FollowerSpec): string | null {
  if (follower.adapter === "hwlab-node-runtime") {
    return hwlabRuntimeLaneSpecForNode(follower.target.lane, follower.target.node).gitopsBranch;
  }
  if (follower.adapter === "agentrun-yaml-lane") {
    return resolveAgentRunLaneTarget({ node: follower.target.node, lane: follower.target.lane }).spec.gitops.branch;
  }
  return null;
}

function workloadReady(spec: NativeWorkloadSpec, workload: Record<string, unknown> | null): boolean {
  if (workload === null) return false;
  const status = asOptionalRecord(workload.status);
  if (spec.kind === "Deployment") {
    const desired = numberOrNull(asOptionalRecord(workload.spec)?.replicas) ?? 1;
    const available = numberOrNull(status?.availableReplicas) ?? 0;
    const updated = numberOrNull(status?.updatedReplicas) ?? 0;
    return available >= desired && updated >= desired;
  }
  const desired = numberOrNull(asOptionalRecord(workload.spec)?.replicas) ?? 1;
  const ready = numberOrNull(status?.readyReplicas) ?? 0;
  return ready >= desired;
}

function workloadSourceCommit(spec: NativeWorkloadSpec, workload: Record<string, unknown> | null): string | null {
  if (workload === null) return null;
  const metadata = asOptionalRecord(workload.metadata);
  const labels = asOptionalRecord(metadata?.labels);
  const annotations = asOptionalRecord(metadata?.annotations);
  const template = asOptionalRecord(asOptionalRecord(workload.spec)?.template);
  const podMetadata = asOptionalRecord(template?.metadata);
  const podLabels = asOptionalRecord(podMetadata?.labels);
  const podAnnotations = asOptionalRecord(podMetadata?.annotations);
  for (const key of spec.sourceCommit.labels) {
    const value = shaOrNull(labels?.[key]);
    if (value !== null) return value;
  }
  for (const key of spec.sourceCommit.annotations) {
    const value = shaOrNull(annotations?.[key]);
    if (value !== null) return value;
  }
  for (const key of spec.sourceCommit.podLabels) {
    const value = shaOrNull(podLabels?.[key]);
    if (value !== null) return value;
  }
  for (const key of spec.sourceCommit.podAnnotations) {
    const value = shaOrNull(podAnnotations?.[key]);
    if (value !== null) return value;
  }
  const containers = Array.isArray(asOptionalRecord(template?.spec)?.containers) ? asOptionalRecord(template?.spec)?.containers : [];
  for (const envName of spec.sourceCommit.env) {
    for (const container of containers as unknown[]) {
      const env = Array.isArray(asOptionalRecord(container)?.env) ? asOptionalRecord(container)?.env : [];
      for (const entry of env as unknown[]) {
        const record = asOptionalRecord(entry);
        if (record?.name !== envName) continue;
        const value = shaOrNull(record.value);
        if (value !== null) return value;
      }
    }
  }
  return null;
}

function latestCondition(status: Record<string, unknown> | null, type: string): Record<string, unknown> | null {
  const conditions = Array.isArray(status?.conditions) ? status.conditions : [];
  for (const condition of conditions as unknown[]) {
    const record = asOptionalRecord(condition);
    if (record?.type === type) return record;
  }
  return null;
}

function shaOrNull(value: unknown): string | null {
  return typeof value === "string" && /^[0-9a-f]{40}$/iu.test(value) ? value : null;
}

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

function stringOrNull(value: unknown): string | null {
  return typeof value === "string" && value.length > 0 ? value : null;
}

function numberOrNull(value: unknown): number | null {
  return typeof value === "number" && Number.isFinite(value) ? value : null;
}
