import {
  OBSERVABILITY_REPORT_SCHEMA_VERSION,
  type ActivityReportSection,
  type ObservabilityReport,
  type ObservabilityReportQuery,
  type ReportAttentionRecord,
  type ReportCoverageGap,
  type ReportCoverageStatus,
  type ReportEvidenceQuery,
  type ReportSourceCoverage,
} from "@overdeck/report-contract";
import { readActivityCached } from "../activity/read";
import { ACTIVITY_SOURCES } from "../activity/registry";
import { ACTIVITY_SEVERITY_ORDER, type ActivityReadOptions, type ActivityReadResponse } from "../activity/types";

const ACTIVITY_REPORT_LIMIT = 2000;

export interface ActivityReportDependencies {
  readActivity(options: ActivityReadOptions): ActivityReadResponse;
  now(): number;
}

const DEFAULT_DEPENDENCIES: ActivityReportDependencies = {
  readActivity: readActivityCached,
  now: Date.now,
};

function sourceHref(sourceId: string, query: ObservabilityReportQuery): string {
  const params: Array<[string, string]> = [["from", query.from], ["to", query.to]];
  if (query.severity) params.push(["severity", query.severity]);
  if (query.q) params.push(["q", query.q]);
  const encoded = params.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
  return `/logs/${encodeURIComponent(sourceId)}?${encoded}`;
}

function evidenceQuery(
  sourceId: string,
  query: ObservabilityReportQuery,
  recordId?: string,
): ReportEvidenceQuery {
  return {
    sourceId,
    from: query.from,
    to: query.to,
    ...(recordId ? { recordId } : {}),
    ...(query.severity ? { severity: query.severity } : {}),
    ...(query.q ? { q: query.q } : {}),
  };
}

function matchingCount(response: ActivityReadResponse, sourceId: string): number {
  return response.sourceCounts.find((entry) => entry.source === sourceId)?.count ?? 0;
}

function sourceProblemReason(source: ActivityReadResponse["coverage"][number]): string | undefined {
  if (source.status === "error") return `${source.label} could not be read.`;
  if (source.status === "absent") return `${source.label} is not present.`;
  return undefined;
}

function reportSource(
  source: ActivityReadResponse["coverage"][number],
  response: ActivityReadResponse,
  query: ObservabilityReportQuery,
): ReportSourceCoverage {
  return {
    id: source.id,
    label: source.label,
    category: source.category,
    authority: source.authority ?? "derived",
    status: source.status,
    storage: source.storage,
    retention: source.retention,
    queryBounds: source.queryBounds,
    coverageFrom: source.coverageFrom,
    earliest: source.earliest,
    latest: source.latest,
    matchingRecords: matchingCount(response, source.id),
    retainedRecords: source.totalRecords,
    skipped: source.skipped,
    ...(sourceProblemReason(source) ? { reason: sourceProblemReason(source) } : {}),
    href: sourceHref(source.id, query),
  };
}

function coverageGaps(
  sources: ReportSourceCoverage[],
  query: ObservabilityReportQuery,
): ReportCoverageGap[] {
  const fromMs = Date.parse(query.from);
  const gaps: ReportCoverageGap[] = [];
  for (const source of sources) {
    if (source.status !== "ok") {
      gaps.push({
        domain: "activity",
        sourceId: source.id,
        from: query.from,
        to: query.to,
        reason: source.reason ?? `${source.label} is ${source.status}.`,
      });
      continue;
    }
    if (source.skipped > 0) {
      gaps.push({
        domain: "activity",
        sourceId: source.id,
        reason: `${source.label} skipped ${source.skipped} retained record${source.skipped === 1 ? "" : "s"}.`,
      });
    }
    const coverageFromMs = source.coverageFrom ? Date.parse(source.coverageFrom) : Number.NaN;
    if (!Number.isFinite(coverageFromMs)) {
      gaps.push({
        domain: "activity",
        sourceId: source.id,
        from: query.from,
        to: query.to,
        reason: `${source.label} does not record an earliest retained timestamp, so full-window coverage cannot be proven.`,
      });
    } else if (coverageFromMs > fromMs) {
      gaps.push({
        domain: "activity",
        sourceId: source.id,
        from: query.from,
        to: source.coverageFrom,
        reason: `${source.label} retained coverage starts after this report begins.`,
      });
    }
  }
  return gaps;
}

function coverageStatus(
  sources: ReportSourceCoverage[],
  gaps: ReportCoverageGap[],
  query: ObservabilityReportQuery,
): ReportCoverageStatus {
  const required = query.sources?.length
    ? sources
    : sources.filter((source) => source.authority === "authoritative");
  if (required.length === 0 || required.every((source) => source.status !== "ok")) return "unavailable";
  const requiredIds = new Set(required.map((source) => source.id));
  return gaps.some((gap) => !gap.sourceId || requiredIds.has(gap.sourceId)) ? "partial" : "complete";
}

function attentionRecords(
  response: ActivityReadResponse,
  sources: ReportSourceCoverage[],
  query: ObservabilityReportQuery,
): ReportAttentionRecord[] {
  const events: ReportAttentionRecord[] = response.events
    .filter((event) => ACTIVITY_SEVERITY_ORDER[event.severity] >= ACTIVITY_SEVERITY_ORDER.warn)
    .map((event) => ({
      id: event.id,
      ts: event.ts,
      kind: "event" as const,
      title: event.title,
      category: event.category,
      severity: event.severity,
      actor: event.actor,
      sourceId: event.source,
      project: event.project,
      host: event.host,
      runtime: event.runtime,
      session: event.session,
      account: event.account,
      durationMs: event.durationMs,
      result: event.result,
      evidenceQuery: evidenceQuery(event.source, query, event.id),
    }));

  const problems: ReportAttentionRecord[] = sources.flatMap((source) => {
    const rows: ReportAttentionRecord[] = [];
    if (source.status !== "ok") {
      rows.push({
        id: `source:${source.id}:${source.status}`,
        kind: "source-error",
        title: `${source.label} is ${source.status}`,
        sourceId: source.id,
        severity: source.status === "error" ? "error" : "warn",
        reason: source.reason ?? `No readable ${source.label} records are available.`,
        evidenceQuery: evidenceQuery(source.id, query),
      });
    }
    if (source.skipped > 0) {
      rows.push({
        id: `source:${source.id}:skipped`,
        kind: "skipped-records",
        title: `${source.label} skipped records`,
        sourceId: source.id,
        severity: "warn",
        count: source.skipped,
        reason: `${source.skipped} retained record${source.skipped === 1 ? " was" : "s were"} unreadable or unsupported.`,
        evidenceQuery: evidenceQuery(source.id, query),
      });
    }
    return rows;
  });

  return [...problems, ...events].sort((left, right) => {
    const leftTs = left.kind === "event" ? Date.parse(left.ts) : Number.POSITIVE_INFINITY;
    const rightTs = right.kind === "event" ? Date.parse(right.ts) : Number.POSITIVE_INFINITY;
    if (rightTs !== leftTs) return rightTs - leftTs;
    return left.id.localeCompare(right.id);
  });
}

export function buildActivityObservabilityReport(
  query: ObservabilityReportQuery,
  dependencies: ActivityReportDependencies = DEFAULT_DEPENDENCIES,
): ObservabilityReport {
  const generatedAt = new Date(dependencies.now()).toISOString();
  const response = dependencies.readActivity({
    from: query.from,
    to: query.to,
    projects: query.projects,
    sourceIds: query.sources,
    severityFloor: query.severity,
    q: query.q,
    limit: ACTIVITY_REPORT_LIMIT,
  });
  const sources = response.coverage.map((source) => reportSource(source, response, query));
  const gaps = coverageGaps(sources, query);
  const status = coverageStatus(sources, gaps, query);
  const healthySources = sources.filter((source) => source.status === "ok").length;
  const problemSourceIds = new Set([
    ...sources.filter((source) => source.status !== "ok" || source.skipped > 0).map((source) => source.id),
    ...gaps.flatMap((gap) => gap.sourceId ? [gap.sourceId] : []),
  ]);
  const sourceProblems = problemSourceIds.size;
  const canCountEvents = status !== "unavailable";
  const logVolume = response.series?.find((series) => series.id === "logVolume");
  const seriesCoverage: ReportCoverageStatus = logVolume?.status === "available" ? status : "unavailable";

  const section: ActivityReportSection = {
    kind: "activity",
    status,
    title: "Activity and source trust",
    metrics: [
      {
        id: "recorded-events",
        label: "Recorded events",
        value: canCountEvents ? response.total : null,
        unit: "count",
        coverage: status,
      },
      {
        id: "failures",
        label: "Failures",
        value: canCountEvents ? (response.errorCount ?? 0) : null,
        unit: "count",
        coverage: status,
      },
      {
        id: "active-sources",
        label: "Readable sources",
        value: sources.length > 0 ? healthySources : null,
        unit: "count",
        numerator: healthySources,
        denominator: sources.length,
        coverage: sources.length > 0 ? "complete" : "unavailable",
      },
      {
        id: "source-problems",
        label: "Source problems",
        value: sources.length > 0 ? sourceProblems : null,
        unit: "count",
        coverage: sources.length > 0 ? "complete" : "unavailable",
      },
    ],
    series: [{
      id: "activity-volume",
      label: "Recorded activity",
      unit: "count",
      coverage: seriesCoverage,
      points: logVolume?.status === "available" ? logVolume.points : [],
      gaps: logVolume?.gaps ?? (logVolume?.reason ? [{ from: query.from, to: query.to, reason: logVolume.reason }] : []),
    }],
    categoryBreakdown: response.categoryCounts,
    attention: attentionRecords(response, sources, query),
    sources,
    truncated: response.truncated,
    errors: sources.filter((source) => source.status === "error").map((source) => `${source.label}: ${source.reason ?? "read failed"}`),
  };

  return {
    schemaVersion: OBSERVABILITY_REPORT_SCHEMA_VERSION,
    query,
    generatedAt,
    coverage: {
      status,
      from: query.from,
      to: query.to,
      generatedAt,
      sources,
      gaps,
    },
    sections: [section],
    filters: {
      projects: (response.projectCounts ?? []).map((entry) => entry.project),
      sources: ACTIVITY_SOURCES.map((source) => ({ id: source.id, label: source.label })),
    },
  };
}
