import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { type SuppressedNotificationRow, type SuppressedNotifications, type SuppressedOptions } from "../types";

export const SUPPRESSED_SOURCE = {
  id: "suppressed",
  label: "suppressed notifications",
  category: "notification",
} as const;

export const SUPPRESSED_SOURCE_ID = SUPPRESSED_SOURCE.id;

// this source is a per-source aggregate and carries no per-attempt timeline — the
// per-attempt history is the `notif-gate` activity source (sources/notifGate.ts).
interface SuppressedRaw {
  program?: unknown;
  summary_template?: unknown;
  summary_sample?: unknown;
  body_sample?: unknown;
  channel?: unknown;
  count?: unknown;
  first?: unknown;
  last?: unknown;
}

export function defaultSuppressedPath(): string {
  const stateHome = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
  return join(stateHome, "notif-gate", "pending.json");
}

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

function asFiniteNumber(value: unknown): number | undefined {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  return undefined;
}

function toIso(value: number): string | undefined {
  if (!Number.isFinite(value)) return undefined;
  return new Date(value * 1000).toISOString();
}

export function readSuppressedNotifications(options: SuppressedOptions = {}): SuppressedNotifications {
  const {
    path = defaultSuppressedPath(),
    readFileImpl = (path) => readFileSync(path, "utf8"),
    existsSyncImpl = existsSync,
  } = options;

  if (!existsSyncImpl(path)) {
    return {
      status: "absent",
      path,
      rows: [],
      sourceCount: 0,
      attemptCount: 0,
      skipped: 0,
    };
  }

  let raw: string;
  try {
    raw = readFileImpl(path);
  } catch (error) {
    return {
      status: "error",
      path,
      rows: [],
      sourceCount: 0,
      attemptCount: 0,
      skipped: 0,
      error: error instanceof Error ? error.message : String(error),
    };
  }

  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch (error) {
    return {
      status: "error",
      path,
      rows: [],
      sourceCount: 0,
      attemptCount: 0,
      skipped: 0,
      error: error instanceof Error ? error.message : String(error),
    };
  }

  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    return {
      status: "error",
      path,
      rows: [],
      sourceCount: 0,
      attemptCount: 0,
      skipped: 0,
      error: "pending.json did not contain the expected object",
    };
  }

  const container = parsed as { entries?: unknown };
  const entries = container.entries;
  if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
    return {
      status: "ok",
      path,
      rows: [],
      sourceCount: 0,
      attemptCount: 0,
      skipped: 0,
    };
  }

  let skipped = 0;
  const rows: SuppressedNotificationRow[] = [];
  let attemptCount = 0;
  let windowFromMs: number | undefined;
  let windowToMs: number | undefined;
  for (const [id, rawRow] of Object.entries(entries)) {
    if (!rawRow || typeof rawRow !== "object" || Array.isArray(rawRow)) {
      skipped += 1;
      continue;
    }

    const row = rawRow as SuppressedRaw;
    const program = asString(row.program);
    const summary = asString(row.summary_template);
    const count = asFiniteNumber(row.count);
    if (program === undefined || summary === undefined || count === undefined) {
      skipped += 1;
      continue;
    }

    const first = asFiniteNumber(row.first);
    const last = asFiniteNumber(row.last);

    const rowFirst = first === undefined ? undefined : toIso(first);
    const rowLast = last === undefined ? undefined : toIso(last);

    const candidate: SuppressedNotificationRow = {
      id,
      program,
      summary,
      count,
    };
    const summarySample = asString(row.summary_sample);
    if (summarySample) candidate.summarySample = summarySample;
    const bodySample = asString(row.body_sample);
    if (bodySample) candidate.bodySample = bodySample;
    if (row.channel !== undefined) {
      const channel = asString(row.channel);
      if (channel) candidate.channel = channel;
    }
    if (rowFirst) candidate.first = rowFirst;
    if (rowLast) candidate.last = rowLast;

    if (first !== undefined && Number.isFinite(first)) {
      const ms = first * 1000;
      if (windowFromMs === undefined || ms < windowFromMs) windowFromMs = ms;
    }
    if (last !== undefined && Number.isFinite(last)) {
      const ms = last * 1000;
      if (windowToMs === undefined || ms > windowToMs) windowToMs = ms;
    }

    rows.push(candidate);
    attemptCount += count;
  }

  rows.sort((left, right) => {
    if (right.count !== left.count) return right.count - left.count;
    return left.id.localeCompare(right.id);
  });

  return {
    status: "ok",
    path,
    rows,
    sourceCount: rows.length,
    attemptCount,
    ...(windowFromMs === undefined ? {} : { windowFrom: new Date(windowFromMs).toISOString() }),
    ...(windowToMs === undefined ? {} : { windowTo: new Date(windowToMs).toISOString() }),
    skipped,
  };
}
