import type { Adapter, FetchLike } from "../adapter";
import type { AdapterResult, Item, Panel } from "../schema";

export type BotTrafficStatus = "live" | "slow" | "down" | "idle";

export interface BotPanelRow {
  id: string;
  name: string;
  project?: string;
  runtimeStatus: string;
  status: BotTrafficStatus;
  statusDetail?: string;
  messages24h: number;
  errors: number;
  cost: number | null;
  messagesTotal: number;
  costTotal: number | null;
  lastActivityAt: string | null;
  lastMessageAt: string | null;
}

export interface BotsPanelData {
  bots: BotPanelRow[];
  /** Latest mirrored activity timestamp across the fleet (ISO), from Botmaster D1. */
  d1LatestAt: string | null;
  /** Age of newest D1 row in hours; null when no mirrored rows exist. */
  mirrorLagHours: number | null;
}

export interface BotmasterAdapterOptions {
  id?: string;
  interval?: number;
  fetchImpl: FetchLike;
  baseUrl: string;
  token: string;
  now?: () => number;
  /** No metric activity within this window marks a previously-active bot silent. */
  downThresholdMs?: number;
  /** Error count in the 24h window at or above this marks a bot slow. */
  slowErrorThreshold?: number;
}

interface BotSummaryRow {
  id: string;
  bot_name: string;
  project_name: string;
  status: string;
  messages24h: number;
  errors24h: number;
  cost24h: number;
  messagesTotal: number;
  errorsTotal: number;
  costTotal: number;
  lastTs: number | null;
  chatMessages24h: number;
  chatMessagesTotal: number;
  lastChatTs: number | null;
  lastMirrorTs: number | null;
  lastPollTs: number | null;
}

const DEFAULT_INTERVAL_MS = 30_000;
const DEFAULT_DOWN_THRESHOLD_MS = 2 * 60 * 60 * 1000;
const DEFAULT_SLOW_ERROR_THRESHOLD = 3;

function roundCost(value: number): number | null {
  if (value <= 0) return null;
  if (value < 0.01) return Math.round(value * 10_000) / 10_000;
  if (value < 0.1) return Math.round(value * 1_000) / 1_000;
  return Math.round(value * 10) / 10;
}

function formatLastActivity(agoMs: number): string {
  const hours = Math.floor(agoMs / 3_600_000);
  if (hours >= 48) {
    const days = Math.floor(hours / 24);
    return `last call ${days}d ago`;
  }
  if (hours >= 1) return `last call ${hours}h ago`;
  const minutes = Math.max(1, Math.floor(agoMs / 60_000));
  return `last call ${minutes}m ago`;
}

function formatStatusLabel(
  runtimeStatus: string,
  traffic: BotTrafficStatus,
  detail?: string,
): string {
  if (runtimeStatus !== "running") return runtimeStatus;
  switch (traffic) {
    case "live":
      return "running · live";
    case "slow":
      return "running · slow";
    case "idle":
      return "running · idle";
    case "down":
      return detail ? `running · ${detail}` : "running · silent";
  }
}

function deriveTrafficStatus(
  runtimeStatus: string,
  summary: Pick<BotSummaryRow, "messagesTotal" | "errors24h" | "lastTs">,
  now: number,
  downThresholdMs: number,
  slowErrorThreshold: number,
): { status: BotTrafficStatus; statusDetail?: string } {
  if (runtimeStatus !== "running") {
    return { status: "down", statusDetail: runtimeStatus };
  }
  if (summary.messagesTotal === 0) {
    if (summary.lastTs !== null && now - summary.lastTs < downThresholdMs) {
      return { status: "live", statusDetail: formatLastActivity(now - summary.lastTs) };
    }
    return { status: "idle" };
  }
  const silentMs = summary.lastTs === null ? Number.POSITIVE_INFINITY : now - summary.lastTs;
  if (silentMs > downThresholdMs) {
    return { status: "down", statusDetail: formatLastActivity(silentMs) };
  }
  if (summary.errors24h >= slowErrorThreshold) {
    return { status: "slow" };
  }
  return { status: "live" };
}

function buildDownItem(
  source: string,
  botId: string,
  label: string,
  statusDetail: string | undefined,
  ts: string,
): Item {
  const detail = statusDetail ?? "no recent activity";
  return {
    id: `botmaster:${botId}:down`,
    source,
    severity: "act",
    kind: "alert",
    title: `${label} is silent`,
    detail,
    ts,
    actions: [],
  };
}

function buildBotsPanel(bots: BotPanelRow[], ts: string, d1LatestAt: string | null, mirrorLagHours: number | null): Panel {
  return {
    id: "bots",
    ts,
    data: { bots, d1LatestAt, mirrorLagHours } satisfies BotsPanelData,
  };
}

export function createBotmasterAdapter(opts: BotmasterAdapterOptions): Adapter {
  const id = opts.id ?? "botmaster";
  const interval = opts.interval ?? DEFAULT_INTERVAL_MS;
  if (!opts.fetchImpl) throw new Error("fetchImpl is required");
  const fetchImpl = opts.fetchImpl;
  const nowFn = opts.now ?? Date.now;
  const downThresholdMs = opts.downThresholdMs ?? DEFAULT_DOWN_THRESHOLD_MS;
  const slowErrorThreshold = opts.slowErrorThreshold ?? DEFAULT_SLOW_ERROR_THRESHOLD;
  const { baseUrl, token } = opts;

  async function request<T>(path: string): Promise<T> {
    const res = await fetchImpl(`${baseUrl}${path}`, {
      headers: { authorization: `Bearer ${token}` },
    });
    if (!res.ok) {
      throw new Error(`botmaster ${path} failed: HTTP ${res.status}`);
    }
    return (await res.json()) as T;
  }

  async function poll(): Promise<AdapterResult> {
    const now = nowFn();
    const nowIso = new Date(now).toISOString();
    const summaries = await request<BotSummaryRow[]>("/api/bots/summary");

    const bots: BotPanelRow[] = [];
    const items: Item[] = [];
    let fleetLatestTs: number | null = null;

    for (const summary of [...summaries].sort((left, right) =>
      left.bot_name.localeCompare(right.bot_name),
    )) {
      const runtimeStatus = summary.status;
      const { status, statusDetail } = deriveTrafficStatus(
        runtimeStatus,
        summary,
        now,
        downThresholdMs,
        slowErrorThreshold,
      );
      const label = formatStatusLabel(runtimeStatus, status, statusDetail);

      const row: BotPanelRow = {
        id: summary.id,
        name: summary.bot_name,
        project: summary.project_name,
        runtimeStatus,
        status,
        statusDetail: label,
        messages24h: summary.messages24h,
        errors: summary.errors24h,
        cost: summary.cost24h > 0 ? roundCost(summary.cost24h) : null,
        messagesTotal: summary.messagesTotal,
        costTotal: summary.costTotal > 0 ? roundCost(summary.costTotal) : null,
        lastActivityAt: summary.lastTs === null ? null : new Date(summary.lastTs).toISOString(),
        lastMessageAt: summary.lastChatTs == null ? null : new Date(summary.lastChatTs).toISOString(),
      };
      bots.push(row);

      if (summary.lastMirrorTs != null && (fleetLatestTs === null || summary.lastMirrorTs > fleetLatestTs)) {
        fleetLatestTs = summary.lastMirrorTs;
      }

      if (
        runtimeStatus === "running" &&
        summary.messagesTotal > 0 &&
        status === "down"
      ) {
        items.push(buildDownItem(id, summary.id, summary.bot_name, statusDetail, nowIso));
      }
    }

    const mirrorLagHours =
      fleetLatestTs === null ? null : Math.round((now - fleetLatestTs) / 3_600_000);
    const d1LatestAt = fleetLatestTs === null ? null : new Date(fleetLatestTs).toISOString();

    return { items, panels: [buildBotsPanel(bots, nowIso, d1LatestAt, mirrorLagHours)] };
  }

  return { id, interval, poll };
}
