import { existsSync, readdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { AgentSessionEntry } from "../../adapters/agent-sessions";
import { createSkipCollector } from "../skipped";
import { type ActivityEvent, type ActivitySourceResult, type AgentSessionsSourceOptions } from "../types";

export const AGENT_SESSIONS_SOURCE = {
  id: "agent-sessions",
  label: "agent sessions",
  category: "agent",
} as const;

export const AGENT_SESSIONS_SOURCE_ID = AGENT_SESSIONS_SOURCE.id;
const AGENT_SESSIONS_SOURCE_LABEL = AGENT_SESSIONS_SOURCE.label;

export function defaultAgentSessionsRoot(): string {
  return process.env.AGENT_SESSIONS_DIR ?? join(homedir(), ".local", "state", "agent-sessions");
}

function asString(value: unknown): string | undefined {
  if (typeof value === "string") {
    const trimmed = value.trim();
    return trimmed.length === 0 ? undefined : trimmed;
  }
  return undefined;
}

type AgentSessionParse =
  | { ok: true; entry: AgentSessionEntry }
  | { ok: false; reason: "not-an-object" | "unsupported-schema" | "missing-fields"; detail?: string };

function safeParseAgentSession(record: unknown): AgentSessionParse {
  if (!record || typeof record !== "object" || Array.isArray(record)) {
    return { ok: false, reason: "not-an-object" };
  }
  const schemaVersion = (record as { schemaVersion?: unknown }).schemaVersion;
  if (schemaVersion !== 1) {
    return { ok: false, reason: "unsupported-schema", detail: `schemaVersion=${String(schemaVersion)}, expected 1` };
  }

  const typed = record as Partial<AgentSessionEntry>;
  for (const field of ["ledgerId", "runtime", "startedAt", "host"] as const) {
    const value = typed[field];
    if (typeof value !== "string" || value.trim().length === 0) {
      return { ok: false, reason: "missing-fields", detail: `${field} is absent or blank` };
    }
  }

  return { ok: true, entry: typed as AgentSessionEntry };
}

function titleForStart(runtime: string, launchedBy: string | undefined): string {
  if (!launchedBy) return `${runtime} session started`;
  return `${runtime} session started by ${launchedBy}`;
}

function titleForEnd(runtime: string, finishReason: string | undefined): string {
  if (!finishReason) return `${runtime} session finished`;
  return `${runtime} session finished — ${finishReason}`;
}

function durationMsFor(startedAt: string, finishedAt: string): number | undefined {
  const startedMs = Date.parse(startedAt);
  const finishedMs = Date.parse(finishedAt);
  if (!Number.isFinite(startedMs) || !Number.isFinite(finishedMs)) return undefined;
  const delta = finishedMs - startedMs;
  return delta >= 0 ? delta : undefined;
}

export function readAgentSessionsSource(options: AgentSessionsSourceOptions = {}): ActivitySourceResult {
  const {
    root = defaultAgentSessionsRoot(),
    existsSyncImpl = existsSync,
    readdirSyncImpl = readdirSync,
    readFileImpl = (path) => readFileSync(path, "utf8"),
    fromMs,
    toMs,
    collectSkipped = false,
  } = options;

  const sessionsDir = join(root, "sessions");
  if (!existsSyncImpl(sessionsDir)) {
    return {
      events: [],
      skippedEntries: [],
      skippedTruncated: false,
      coverage: {
        id: AGENT_SESSIONS_SOURCE_ID,
        label: AGENT_SESSIONS_SOURCE_LABEL,
        category: AGENT_SESSIONS_SOURCE.category,
        path: sessionsDir,
        status: "absent",
        records: 0,
        totalRecords: 0,
        skipped: 0,
      },
    };
  }

  let names: string[];
  try {
    names = readdirSyncImpl(sessionsDir);
  } catch {
    return {
      events: [],
      skippedEntries: [],
      skippedTruncated: false,
      coverage: {
        id: AGENT_SESSIONS_SOURCE_ID,
        label: AGENT_SESSIONS_SOURCE_LABEL,
        category: AGENT_SESSIONS_SOURCE.category,
        path: sessionsDir,
        status: "absent",
        records: 0,
        totalRecords: 0,
        skipped: 0,
      },
    };
  }

  let totalRecords = 0;
  let records = 0;
  const skips = createSkipCollector(AGENT_SESSIONS_SOURCE_ID, collectSkipped);
  let earliestMs: number | undefined;
  let latestMs: number | undefined;
  let ordinal = 0;
  const events: ActivityEvent[] = [];
  const sortedNames = [...names].sort();

  for (const name of sortedNames) {
    if (!name.endsWith(".json")) {
      continue;
    }
    const path = join(sessionsDir, name);

    let text: string;
    try {
      text = readFileImpl(path);
    } catch (error) {
      skips.record("unreadable-file", { path }, undefined, error instanceof Error ? error.message : String(error));
      continue;
    }

    let parsed: unknown;
    try {
      parsed = JSON.parse(text);
    } catch {
      skips.record("unparseable-json", { path }, text);
      continue;
    }

    const parseResult = safeParseAgentSession(parsed);
    if (!parseResult.ok) {
      skips.record(parseResult.reason, { path }, text, parseResult.detail);
      continue;
    }
    const record = parseResult.entry;

    totalRecords += 1;
    ordinal += 1;

    const startedAtMs = Date.parse(record.startedAt);
    if (Number.isFinite(startedAtMs)) {
      if (earliestMs === undefined || startedAtMs < earliestMs) earliestMs = startedAtMs;
      if (latestMs === undefined || startedAtMs > latestMs) latestMs = startedAtMs;
    } else {
      skips.record("invalid-timestamp", { path }, text, `startedAt=${record.startedAt}`);
      continue;
    }

    const startEvent: ActivityEvent = {
      id: `${AGENT_SESSIONS_SOURCE_ID}:${record.ledgerId}:start`,
      ts: record.startedAt,
      category: AGENT_SESSIONS_SOURCE.category,
      source: AGENT_SESSIONS_SOURCE_ID,
      actor: "agent",
      severity: "info",
      title: titleForStart(record.runtime, asString(record.launchedBy)),
      lifecycle: "started",
      result: "unknown",
      runtime: record.runtime,
      host: record.host,
      session: record.ledgerId,
      dedupeKey: `${record.runtime}:started`,
    };

    const project = asString(record.project);
    if (project) {
      startEvent.project = project;
    }

    const account = asString(record.account);
    if (account) {
      startEvent.account = account;
    }

    const startedEventInRange = Number.isFinite(startedAtMs)
      && (fromMs === undefined || startedAtMs >= fromMs)
      && (toMs === undefined || startedAtMs <= toMs);
    if (startedEventInRange) {
      events.push(startEvent);
      records += 1;
    }

    const finishedAt = asString(record.finishedAt);
    if (!finishedAt) {
      continue;
    }

    const finishedAtMs = Date.parse(finishedAt);
    if (Number.isFinite(finishedAtMs)) {
      if (earliestMs === undefined || finishedAtMs < earliestMs) earliestMs = finishedAtMs;
      if (latestMs === undefined || finishedAtMs > latestMs) latestMs = finishedAtMs;
    } else {
      continue;
    }

    const finishReason = asString(record.finishReason)?.toLowerCase();
    const finishOutcome: Pick<ActivityEvent, "lifecycle" | "result" | "severity"> = finishReason && /cancel|interrupt|abort/.test(finishReason)
      ? { lifecycle: "cancelled", result: "cancelled", severity: "notice" }
      : finishReason && /fail|error|crash|kill|oom/.test(finishReason)
        ? { lifecycle: "failed", result: "failure", severity: "error" }
        : finishReason && /^(success|succeeded|complete|completed|done)$/.test(finishReason)
          ? { lifecycle: "completed", result: "success", severity: "info" }
          : { lifecycle: "completed", result: "unknown", severity: "info" };
    const endEvent: ActivityEvent = {
      id: `${AGENT_SESSIONS_SOURCE_ID}:${record.ledgerId}:end`,
      ts: finishedAt,
      category: AGENT_SESSIONS_SOURCE.category,
      source: AGENT_SESSIONS_SOURCE_ID,
      actor: "agent",
      severity: finishOutcome.severity,
      title: titleForEnd(record.runtime, asString(record.finishReason)),
      lifecycle: finishOutcome.lifecycle,
      result: finishOutcome.result,
      runtime: record.runtime,
      host: record.host,
      session: record.ledgerId,
      dedupeKey: `${record.runtime}:finished`,
    };

    const durationMs = durationMsFor(record.startedAt, finishedAt);
    if (durationMs !== undefined) {
      endEvent.durationMs = durationMs;
    }
    if (project) {
      endEvent.project = project;
    }
    if (account) {
      endEvent.account = account;
    }

    const finishedEventInRange = Number.isFinite(finishedAtMs)
      && (fromMs === undefined || finishedAtMs >= fromMs)
      && (toMs === undefined || finishedAtMs <= toMs);
    if (finishedEventInRange) {
      events.push(endEvent);
      records += 1;
    }
  }

  return {
    events,
    skippedEntries: skips.entries,
    skippedTruncated: skips.truncated,
    coverage: {
      id: AGENT_SESSIONS_SOURCE_ID,
      label: AGENT_SESSIONS_SOURCE_LABEL,
      category: AGENT_SESSIONS_SOURCE.category,
      path: sessionsDir,
      status: "ok",
      records,
      totalRecords,
      skipped: skips.count,
      ...(earliestMs === undefined ? {} : {
        earliest: new Date(earliestMs).toISOString(),
        latest: new Date(latestMs ?? earliestMs).toISOString(),
      }),
    },
  };
}
