import { join } from "node:path";
import {
  readActionsSource,
  ACTIONS_SOURCE,
  defaultActionsPath,
} from "./sources/actions";
import {
  readAgentSessionsSource,
  AGENT_SESSIONS_SOURCE,
  defaultAgentSessionsRoot,
} from "./sources/agentSessions";
import {
  readControllerSource,
  CONTROLLER_SOURCE,
  defaultControllerPath,
} from "./sources/controller";
import {
  readFleetHardenSource,
  FLEETHARDEN_SOURCE,
  defaultFleetHardenPath,
} from "./sources/fleetHarden";
import {
  readLandqSource,
  LANDQ_SOURCE,
  defaultRepoRoots,
} from "./sources/landq";
import {
  readNotificationsSource,
  NOTIFICATIONS_SOURCE,
  defaultNotificationsPath,
} from "./sources/notifications";
import {
  readNotifGateSource,
  NOTIF_GATE_SOURCE,
  defaultNotifGatePath,
} from "./sources/notifGate";
import {
  readReaperSource,
  REAPER_SOURCE,
  defaultReaperPath,
} from "./sources/reaper";
import type { ActivityCategory, ActivityEvent, ActivityReadOptions, ActivitySeriesId, ActivitySourceAuthority, ActivitySourceResult } from "./types";
import { defaultKubernetesActivityPath, KUBERNETES_SOURCE, readKubernetesSource } from "./sources/kubernetes";

export interface ActivitySourceWindow {
  fromMs?: number;
  toMs?: number;
  collectSkipped?: boolean;
}

/** What `readActivity` needs to report a source that threw before producing coverage. */
export interface ActivitySourceFallback {
  id: string;
  label: string;
  category: ActivityCategory;
  path: string;
}

export interface PreparedActivitySource {
  fallback: ActivitySourceFallback;
  read(): ActivitySourceResult;
}

export interface ActivitySourceDescriptor {
  id: string;
  label: string;
  category: ActivityCategory;
  authority: ActivitySourceAuthority;
  storage: string;
  retention: string;
  queryBounds: string;
  series: ActivitySeriesId[];
  normalize(event: ActivityEvent, sourcePath: string): ActivityEvent;
  prepare(window: ActivitySourceWindow, sources: ActivityReadOptions["sources"]): PreparedActivitySource;
}

function normalizeDefault(event: ActivityEvent, sourcePath: string): ActivityEvent {
  const buildKey = typeof event.detail?.buildKey === "string" ? event.detail.buildKey : undefined;
  const workloadUid = typeof event.detail?.workloadUid === "string" ? event.detail.workloadUid : undefined;
  return {
    ...event,
    lifecycle: event.lifecycle ?? (event.rc === undefined ? "unknown" : event.rc === 0 ? "completed" : "failed"),
    result: event.result ?? (event.rc === undefined ? "unknown" : event.rc === 0 ? "success" : "failure"),
    correlation: event.correlation ?? { project: event.project, host: event.host, sessionId: event.session, buildKey, workloadUid },
    evidence: event.evidence ?? [{ sourceId: event.source, sourcePath, recordId: event.id, href: `/logs/${encodeURIComponent(event.source)}?event=${encodeURIComponent(event.id)}` }],
  };
}

const FILE_SOURCE_META = {
  authority: "authoritative" as const,
  storage: "Local source file",
  retention: "Source-managed; coverage reports observed bounds only.",
  queryBounds: "Collector filters records within the requested timestamp range.",
  series: ["logVolume"] as ActivitySeriesId[],
  normalize: normalizeDefault,
};

function normalizeKubernetes(event: ActivityEvent, sourcePath: string): ActivityEvent {
  const normalized = normalizeDefault(event, sourcePath);
  return { ...normalized, lifecycle: event.lifecycle ?? normalized.lifecycle, result: event.result ?? normalized.result };
}

const KUBERNETES_META = {
  authority: "derived" as const,
  storage: "Periodic Kubernetes snapshots persisted locally",
  retention: "Snapshot history exists only while the polling recorder retains it.",
  queryBounds: "Observed snapshots within the requested timestamp range; not Kubernetes audit history.",
  series: ["logVolume"] as ActivitySeriesId[],
  normalize: normalizeKubernetes,
};

const AGENT_SESSIONS_META = {
  ...FILE_SOURCE_META,
  storage: "Local agent session records",
  series: ["logVolume", "activeAgents"] as ActivitySeriesId[],
};

export const ACTIVITY_SOURCES: ActivitySourceDescriptor[] = [
  {
    ...KUBERNETES_SOURCE,
    ...KUBERNETES_META,
    prepare(window, sources) {
      const options = { ...window, ...(sources?.kubernetes ?? {}), path: sources?.kubernetes?.path ?? defaultKubernetesActivityPath() };
      return { fallback: { ...KUBERNETES_SOURCE, path: options.path }, read: () => readKubernetesSource(options) };
    },
  },
  {
    ...FILE_SOURCE_META,
    ...CONTROLLER_SOURCE,
    prepare(window, sources) {
      const options = {
        ...window,
        ...(sources?.controller ?? {}),
        path: sources?.controller?.path ?? defaultControllerPath(),
      };
      return {
        fallback: { ...CONTROLLER_SOURCE, path: options.path },
        read: () => readControllerSource(options),
      };
    },
  },
  {
    ...FILE_SOURCE_META,
    ...LANDQ_SOURCE,
    prepare(window, sources) {
      const repoRoots = sources?.landq?.repoRoots ?? defaultRepoRoots();
      const options = { ...window, ...(sources?.landq ?? {}), repoRoots };
      return {
        fallback: {
          ...LANDQ_SOURCE,
          path: repoRoots.map((repoRoot) => join(repoRoot, ".git", "harness", "landq", "log")).join(";"),
        },
        read: () => readLandqSource(options),
      };
    },
  },
  {
    ...FILE_SOURCE_META,
    ...ACTIONS_SOURCE,
    prepare(window, sources) {
      const options = {
        ...window,
        ...(sources?.actions ?? {}),
        path: sources?.actions?.path ?? defaultActionsPath(),
      };
      return {
        fallback: { ...ACTIONS_SOURCE, path: options.path },
        read: () => readActionsSource(options),
      };
    },
  },
  {
    ...AGENT_SESSIONS_SOURCE,
    ...AGENT_SESSIONS_META,
    prepare(window, sources) {
      const options = {
        ...window,
        ...(sources?.agentSessions ?? {}),
        root: sources?.agentSessions?.root ?? defaultAgentSessionsRoot(),
      };
      return {
        fallback: { ...AGENT_SESSIONS_SOURCE, path: join(options.root, "sessions") },
        read: () => readAgentSessionsSource(options),
      };
    },
  },
  {
    ...FILE_SOURCE_META,
    ...FLEETHARDEN_SOURCE,
    prepare(window, sources) {
      const options = {
        ...window,
        ...(sources?.fleetHarden ?? {}),
        path: sources?.fleetHarden?.path ?? defaultFleetHardenPath(),
      };
      return {
        fallback: { ...FLEETHARDEN_SOURCE, path: options.path },
        read: () => readFleetHardenSource(options),
      };
    },
  },
  {
    ...FILE_SOURCE_META,
    ...NOTIFICATIONS_SOURCE,
    prepare(window, sources) {
      const options = {
        ...window,
        ...(sources?.notifications ?? {}),
        path: sources?.notifications?.path ?? defaultNotificationsPath(),
      };
      return {
        fallback: { ...NOTIFICATIONS_SOURCE, path: options.path },
        read: () => readNotificationsSource(options),
      };
    },
  },
  {
    ...FILE_SOURCE_META,
    ...NOTIF_GATE_SOURCE,
    prepare(window, sources) {
      const options = {
        ...window,
        ...(sources?.notifGate ?? {}),
        path: sources?.notifGate?.path ?? defaultNotifGatePath(),
      };
      return {
        fallback: { ...NOTIF_GATE_SOURCE, path: options.path },
        read: () => readNotifGateSource(options),
      };
    },
  },
  {
    ...FILE_SOURCE_META,
    ...REAPER_SOURCE,
    prepare(window, sources) {
      const options = {
        ...window,
        ...(sources?.reaper ?? {}),
        path: sources?.reaper?.path ?? defaultReaperPath(),
      };
      return {
        fallback: { ...REAPER_SOURCE, path: options.path },
        read: () => readReaperSource(options),
      };
    },
  },
];

export const ACTIVITY_SOURCE_IDS: string[] = ACTIVITY_SOURCES.map((source) => source.id);

export function activitySourceById(id: string): ActivitySourceDescriptor | undefined {
  return ACTIVITY_SOURCES.find((source) => source.id === id);
}
