import type { ActivityEvent, ActivitySourceCoverage } from "@overdeck/activity-contract";
import type {
  ObservabilityReportQuery,
  ReliabilityFailureGroup,
  ReliabilityReportSection,
  ReportCountBreakdown,
  ReportCoverageGap,
  ReportCoverageStatus,
} from "@overdeck/report-contract";
import type { Incident } from "../incidents/incident-service";
import type { GithubCheckRow } from "../requests/requests-store";

const RELIABILITY_RECORD_LIMIT = 100;
const FAILED_CHECK_CONCLUSIONS = new Set([
  "failure",
  "timed_out",
  "action_required",
  "startup_failure",
  "stale",
]);

export interface ReliabilityReportInput {
  incidents?: Incident[];
  incidentsComplete?: boolean;
  incidentCoverageStale?: boolean;
  ciChecks?: GithubCheckRow[];
  activityEvents?: ActivityEvent[];
  activitySources?: ActivitySourceCoverage[];
  activityTruncated?: boolean;
}

function timestamp(value: string | null | undefined): number | undefined {
  if (!value) return undefined;
  const parsed = Date.parse(value);
  return Number.isFinite(parsed) ? parsed : undefined;
}

function incidentInRange(incident: Incident, fromMs: number, toMs: number): boolean {
  const createdAt = timestamp(incident.createdAt);
  const resolvedAt = timestamp(incident.resolvedAt);
  if (createdAt === undefined || createdAt > toMs) return false;
  return resolvedAt === undefined || resolvedAt >= fromMs;
}

function checkInRange(check: GithubCheckRow, fromMs: number, toMs: number): boolean {
  const at = timestamp(check.completed_at) ?? timestamp(check.started_at) ?? timestamp(check.observed_at);
  return at !== undefined && at >= fromMs && at <= toMs;
}

function isFailure(event: ActivityEvent): boolean {
  return event.result === "failure" || event.lifecycle === "failed" || event.severity === "error";
}

function weightedBreakdown(values: Array<{ key: string | undefined; count: number }>): ReportCountBreakdown[] {
  const counts = new Map<string, number>();
  for (const value of values) {
    if (!value.key) continue;
    counts.set(value.key, (counts.get(value.key) ?? 0) + value.count);
  }
  return [...counts]
    .map(([key, count]) => ({ key, label: key, count }))
    .sort((left, right) => right.count - left.count || left.label.localeCompare(right.label));
}

function failureGroups(
  incidents: Incident[],
  checks: GithubCheckRow[],
  events: ActivityEvent[],
  sourceLabels: Map<string, string>,
): ReliabilityFailureGroup[] {
  const rows: Array<ReliabilityFailureGroup & { timestamps: string[] }> = [];
  for (const incident of incidents) {
    const failureClass = incident.dispatch.failureClass?.trim();
    const at = incident.dispatch.completedAt ?? incident.updatedAt ?? incident.createdAt;
    if (!failureClass || !at) continue;
    rows.push({
      key: `incident:${failureClass}`,
      label: `Incident: ${failureClass}`,
      source: "Incidents",
      category: "service",
      count: 1,
      firstAt: at,
      latestAt: at,
      timestamps: [at],
      service: incident.incidentType ?? undefined,
      href: "/incidents",
    });
  }
  for (const check of checks) {
    const conclusion = check.conclusion?.toLocaleLowerCase();
    const at = check.completed_at ?? check.observed_at;
    if (!conclusion || !FAILED_CHECK_CONCLUSIONS.has(conclusion) || !at) continue;
    rows.push({
      key: `ci:${check.repo}:${check.name}:${conclusion}`,
      label: `CI check: ${check.name}`,
      source: "GitHub checks",
      category: "ci",
      count: 1,
      firstAt: at,
      latestAt: at,
      timestamps: [at],
      project: check.repo,
      service: check.name,
      href: "/requests",
    });
  }
  for (const event of events) {
    if (!isFailure(event)) continue;
    const stableKey = event.dedupeKey?.trim();
    rows.push({
      key: stableKey ? `activity:${event.source}:${stableKey}` : `activity-record:${event.id}`,
      label: `${sourceLabels.get(event.source) ?? "Recorded source"} failure`,
      source: sourceLabels.get(event.source) ?? "Recorded source",
      category: event.category,
      count: 1,
      firstAt: event.ts,
      latestAt: event.ts,
      timestamps: [event.ts],
      project: event.project,
      host: event.host,
      service: event.runtime,
      href: event.evidence?.[0]?.href,
    });
  }

  const grouped = new Map<string, ReliabilityFailureGroup & { timestamps: string[] }>();
  for (const row of rows) {
    const dimensionKey = [row.key, row.project ?? "", row.host ?? "", row.service ?? ""].join(" ");
    const current = grouped.get(dimensionKey);
    if (!current) {
      grouped.set(dimensionKey, row);
      continue;
    }
    current.count += row.count;
    current.timestamps.push(...row.timestamps);
    current.timestamps.sort();
    current.firstAt = current.timestamps[0]!;
    current.latestAt = current.timestamps.at(-1)!;
  }
  return [...grouped.values()]
    .map(({ timestamps: _timestamps, ...group }) => group)
    .sort((left, right) => right.count - left.count || right.latestAt.localeCompare(left.latestAt));
}

export function buildReliabilityReportSection(
  query: ObservabilityReportQuery,
  input: ReliabilityReportInput,
): ReliabilityReportSection {
  const fromMs = Date.parse(query.from);
  const toMs = Date.parse(query.to);
  const incidents = (input.incidents ?? [])
    .filter((incident) => incidentInRange(incident, fromMs, toMs))
    .filter((incident) => !query.q || [incident.title, incident.incidentType, incident.dispatch.failureClass]
      .some((value) => value?.toLocaleLowerCase().includes(query.q!.toLocaleLowerCase())))
    .sort((left, right) => (timestamp(right.updatedAt) ?? 0) - (timestamp(left.updatedAt) ?? 0));
  const checks = (input.ciChecks ?? []).filter((check) =>
    checkInRange(check, fromMs, toMs)
    && (!query.projects?.length || query.projects.some((project) => check.repo === project || check.repo.endsWith(`/${project}`)))
    && (!query.q || [check.repo, check.name, check.conclusion]
      .some((value) => value?.toLocaleLowerCase().includes(query.q!.toLocaleLowerCase()))));
  const events = (input.activityEvents ?? []).filter((event) =>
    timestamp(event.ts) !== undefined
    && timestamp(event.ts)! >= fromMs
    && timestamp(event.ts)! <= toMs
    && (!query.projects?.length || (event.project !== undefined && query.projects.includes(event.project)))
    && (!query.q || [event.project, event.host, event.runtime]
      .some((value) => value?.toLocaleLowerCase().includes(query.q!.toLocaleLowerCase()))));
  const sourceLabels = new Map((input.activitySources ?? []).map((source) => [source.id, source.label]));
  const groups = failureGroups(incidents, checks, events, sourceLabels);
  const gaps: ReportCoverageGap[] = [];
  if (!input.incidents) gaps.push({ domain: "reliability", sourceId: "incidents", reason: "Incident records are unavailable for this report." });
  if (!input.ciChecks) gaps.push({ domain: "reliability", sourceId: "github-checks", reason: "CI check records are unavailable for this report." });
  if (!input.activityEvents) gaps.push({ domain: "reliability", sourceId: "operations", reason: "Service, guard, reaper, deploy, and buildbox activity is unavailable for this report." });
  if (input.incidentCoverageStale) gaps.push({ domain: "reliability", sourceId: "incidents", reason: "Incident records are stale." });
  if (input.incidentsComplete === false) gaps.push({ domain: "reliability", sourceId: "incidents", reason: "The incident registry has more matching records than this live report retained." });
  if (input.activityTruncated) gaps.push({ domain: "reliability", sourceId: "operations", reason: "Operational failure records reached the live report limit." });
  const activitySourceProblems = (input.activitySources ?? []).filter((source) => source.status !== "ok" || source.skipped > 0);
  if (activitySourceProblems.length > 0) {
    gaps.push({
      domain: "reliability",
      sourceId: "operations",
      reason: `${activitySourceProblems.length.toLocaleString()} selected operational ${activitySourceProblems.length === 1 ? "source is" : "sources are"} unreadable, incomplete, or out of date.`,
    });
  }
  const sourceCount = [input.incidents, input.ciChecks, input.activityEvents].filter((source) => source !== undefined).length;
  const incomplete = input.incidentsComplete === false || input.activityTruncated === true || activitySourceProblems.length > 0;
  let status: ReportCoverageStatus = sourceCount === 0
    ? "unavailable"
    : sourceCount < 3 || incomplete
      ? "partial"
      : input.incidentCoverageStale
        ? "stale"
        : "complete";
  const resolved = incidents.filter((incident) => incident.state === "resolved");
  const unresolved = incidents.filter((incident) => incident.active && incident.state !== "resolved");
  const resolvedWithoutDuration = resolved.filter((incident) => {
    const createdAt = timestamp(incident.createdAt);
    const resolvedAt = timestamp(incident.resolvedAt);
    return createdAt === undefined || resolvedAt === undefined || resolvedAt < createdAt;
  });
  if (resolvedWithoutDuration.length > 0) {
    gaps.push({
      domain: "reliability",
      sourceId: "incidents",
      metric: "recovery-duration",
      reason: `${resolvedWithoutDuration.length.toLocaleString()} resolved ${resolvedWithoutDuration.length === 1 ? "incident lacks" : "incidents lack"} explicit lifecycle timestamps, so recovery duration is unavailable.`,
    });
    status = "partial";
  }
  const visibleIncidents = incidents.slice(0, RELIABILITY_RECORD_LIMIT).map((incident) => {
    const createdAt = timestamp(incident.createdAt);
    const resolvedAt = timestamp(incident.resolvedAt);
    return {
      id: incident.id,
      title: incident.title,
      ...(incident.priority ? { priority: incident.priority } : {}),
      ...(incident.incidentType ? { incidentType: incident.incidentType } : {}),
      state: incident.state,
      ...(incident.createdAt ? { createdAt: incident.createdAt } : {}),
      ...(incident.resolvedAt ? { resolvedAt: incident.resolvedAt } : {}),
      durationMs: createdAt !== undefined && resolvedAt !== undefined && resolvedAt >= createdAt ? resolvedAt - createdAt : null,
      ageMs: createdAt !== undefined && resolvedAt === undefined ? Math.max(0, toMs - createdAt) : null,
      ...(incident.dispatch.failureClass ? { failureClass: incident.dispatch.failureClass } : {}),
      href: "/incidents",
    };
  });
  const truncated = incidents.length > RELIABILITY_RECORD_LIMIT || groups.length > RELIABILITY_RECORD_LIMIT
    || input.incidentsComplete === false || input.activityTruncated === true;

  return {
    kind: "reliability",
    status,
    title: "Reliability and recovery",
    metrics: [
      { id: "incidents", label: "Incidents in window", value: input.incidents ? incidents.length : null, unit: "count", coverage: input.incidents ? (input.incidentsComplete === false ? "partial" : "complete") : "unavailable" },
      { id: "unresolved-incidents", label: "Unresolved incidents", value: input.incidents ? unresolved.length : null, unit: "count", coverage: input.incidents ? (input.incidentsComplete === false ? "partial" : "complete") : "unavailable" },
      { id: "resolved-incidents", label: "Resolved incidents", value: input.incidents ? resolved.length : null, unit: "count", coverage: input.incidents ? (input.incidentsComplete === false ? "partial" : "complete") : "unavailable" },
      { id: "recorded-failures", label: "Recorded failures", value: sourceCount > 0 ? groups.reduce((total, group) => total + group.count, 0) : null, unit: "count", coverage: sourceCount === 3 && !incomplete ? "complete" : sourceCount === 0 ? "unavailable" : "partial" },
    ],
    incidents: visibleIncidents,
    failureGroups: groups.slice(0, RELIABILITY_RECORD_LIMIT),
    resolvedDurationSeries: visibleIncidents.flatMap((incident) => incident.durationMs !== null && incident.resolvedAt
      ? [{ ts: incident.resolvedAt, value: incident.durationMs, incidentId: incident.id }]
      : []),
    projectBreakdown: weightedBreakdown(groups.map((group) => ({ key: group.project, count: group.count }))),
    hostBreakdown: weightedBreakdown(groups.map((group) => ({ key: group.host, count: group.count }))),
    serviceBreakdown: weightedBreakdown(groups.map((group) => ({ key: group.service, count: group.count }))),
    gaps,
    truncated,
    errors: [],
  };
}
