import type {
  Board,
  Card,
  CardFile,
  CardHistoryResponse,
  CardMessage,
  Column,
  KanbanModule,
  Metrics,
  MetricsFilter,
  ModuleLog,
  ModuleTestResult,
  Notification,
  Sticker,
  User,
} from "./types";
import { fetchJSON as registryFetchJSON, HTTPError } from "@fn_library/infra/fetch_json";

export { HTTPError };

const BASE = "/api";

function fetchJSON<T>(path: string, init?: RequestInit): Promise<T> {
  return registryFetchJSON<T>(path, init, BASE);
}

export function getBoard(): Promise<Board> {
  return fetchJSON("/board");
}

export function getFlags(): Promise<Record<string, boolean>> {
  return fetchJSON("/flags");
}

export function getVersion(): Promise<{ version: string }> {
  return fetchJSON("/version");
}

export function createColumn(name: string): Promise<Column> {
  return fetchJSON("/columns", { method: "POST", body: JSON.stringify({ name }) });
}

export interface UpdateColumnInput {
  name?: string;
  position?: number;
  location?: "board" | "sidebar";
  width?: number;
  wip_limit?: number;
  is_done?: boolean;
  max_time_minutes?: number;
}

export function updateColumn(id: string, patch: UpdateColumnInput): Promise<void> {
  return fetchJSON(`/columns/${id}`, {
    method: "PATCH",
    body: JSON.stringify(patch),
  });
}

export function deleteColumn(id: string): Promise<void> {
  return fetchJSON(`/columns/${id}`, { method: "DELETE" });
}

export function reorderColumns(ids: string[]): Promise<void> {
  return fetchJSON("/columns/reorder", { method: "POST", body: JSON.stringify({ ids }) });
}

export interface CreateCardInput {
  column_id: string;
  requester?: string;
  title: string;
  description?: string;
  assignee_id?: string | null;
  tags?: string[];
}

export function createCard(input: CreateCardInput): Promise<Card> {
  return fetchJSON("/cards", { method: "POST", body: JSON.stringify(input) });
}

export interface UpdateCardInput {
  requester?: string;
  title?: string;
  description?: string;
  color?: string;
  locked?: boolean;
  assignee_id?: string | null;
  tags?: string[];
  deadline?: string | null;
}

export function updateCard(id: string, patch: UpdateCardInput): Promise<void> {
  return fetchJSON(`/cards/${id}`, { method: "PATCH", body: JSON.stringify(patch) });
}

export function deleteCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}`, { method: "DELETE" });
}

export function updateCardStickers(id: string, stickers: Sticker[]): Promise<void> {
  return fetchJSON(`/cards/${id}/stickers`, {
    method: "PUT",
    body: JSON.stringify({ stickers }),
  });
}

export function listTrash(): Promise<Card[]> {
  return fetchJSON("/trash");
}

export function restoreCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}/restore`, { method: "POST" });
}

export function purgeCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}/purge`, { method: "DELETE" });
}

export function listArchive(): Promise<Card[]> {
  return fetchJSON("/archive");
}

export function archiveCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}/archive`, { method: "POST" });
}

export function unarchiveCard(id: string): Promise<void> {
  return fetchJSON(`/cards/${id}/unarchive`, { method: "POST" });
}

export interface DailyReport {
  date: string;
  tz: string;
  start_ts: string;
  end_ts: string;
  kpis: {
    done: number;
    created: number;
    moves: number;
    blocked_ms: number;
    deadlines_met: number;
    deadlines_missed: number;
    reopened: number;
    archived_auto: number;
    archived_manual: number;
  };
  top_assignees_done: { user_id: string; name: string; count: number }[];
  top_assignees_created: { user_id: string; name: string; count: number }[];
  top_requesters_added: { name: string; count: number }[];
  top_requesters_done: { name: string; count: number }[];
  done_cards: {
    id: string;
    seq_num: number;
    title: string;
    requester: string;
    assignee_id: string | null;
    assignee_name: string | null;
    tags: string[];
    column_id: string;
    column_name: string;
    completed_at: string;
    created_at: string;
    lead_time_ms: number;
    color: string;
  }[];
  reopened_cards: {
    card_id: string;
    title: string;
    seq_num: number;
    from_column: string;
    to_column: string;
    ts: string;
    actor_id: string | null;
    actor_name: string | null;
  }[];
  stale_cards: {
    d7: StaleEntry[];
    d14: StaleEntry[];
    d30: StaleEntry[];
  };
  lead_time: { avg_ms: number; p50_ms: number; p95_ms: number; samples: number };
  hourly_moves: number[];
  deadlines: {
    met: number;
    missed: number;
    list: {
      card_id: string;
      title: string;
      seq_num: number;
      deadline: string;
      completed_at: string;
      late_ms: number;
    }[];
  };
  tags_done: { name: string; count: number }[];
  archived_today: number;
}

export interface StaleEntry {
  card_id: string;
  title: string;
  seq_num: number;
  column_id: string;
  column_name: string;
  entered_at: string;
  days: number;
}

export function dailyReport(date: string, tz?: string): Promise<DailyReport> {
  const params = new URLSearchParams({ date });
  if (tz) params.set("tz", tz);
  return fetchJSON(`/reports/daily?${params.toString()}`);
}

export interface DailySummary {
  date: string;
  summary: string;
  prompt?: string;
  model?: string;
  generated_at?: string;
  generated_by?: string | null;
  exists: boolean;
}

export function getDailySummary(date: string): Promise<DailySummary> {
  return fetchJSON(`/reports/daily/summary?date=${encodeURIComponent(date)}`);
}

export function generateDailySummary(date: string, tz?: string): Promise<DailySummary> {
  const params = new URLSearchParams({ date });
  if (tz) params.set("tz", tz);
  return fetchJSON(`/reports/daily/summary?${params.toString()}`, { method: "POST" });
}

export function getSetting(key: string): Promise<{ key: string; value: string }> {
  return fetchJSON(`/settings/${encodeURIComponent(key)}`);
}

export function setSetting(key: string, value: string): Promise<void> {
  return fetchJSON(`/settings/${encodeURIComponent(key)}`, {
    method: "PUT",
    body: JSON.stringify({ value }),
  });
}

export function moveCard(id: string, column_id: string, ordered_ids: string[]): Promise<void> {
  return fetchJSON(`/cards/${id}/move`, {
    method: "POST",
    body: JSON.stringify({ column_id, ordered_ids }),
  });
}

export function cardHistory(id: string): Promise<CardHistoryResponse> {
  return fetchJSON(`/cards/${id}/history`);
}

export function listCardMessages(id: string): Promise<CardMessage[]> {
  return fetchJSON(`/cards/${id}/messages`);
}

export function createCardMessage(id: string, body: string): Promise<CardMessage> {
  return fetchJSON(`/cards/${id}/messages`, {
    method: "POST",
    body: JSON.stringify({ body }),
  });
}

export function deleteCardMessage(cardId: string, messageId: string): Promise<void> {
  return fetchJSON(`/cards/${cardId}/messages/${messageId}`, { method: "DELETE" });
}

export function duplicateCard(id: string): Promise<Card> {
  return fetchJSON(`/cards/${id}/duplicate`, { method: "POST" });
}

export interface ChatMessage {
  role: "user" | "assistant";
  content: string;
}

export interface ChatToolCall {
  tool: string;
  ok: boolean;
  error?: string;
  input?: unknown;
}

// WebSocket streaming events emitted by /api/chat/ws.
export type ChatStreamEvent =
  | { type: "delta"; text: string }
  | { type: "tool_use"; tool_id: string; tool: string; input?: unknown }
  | { type: "tool_result"; tool_id: string; result?: string; is_error?: boolean }
  | { type: "result"; text?: string; is_error?: boolean }
  | { type: "done"; board_changed?: boolean }
  | { type: "error"; error: string };

// chatWSURL builds the absolute ws:// or wss:// URL of the streaming endpoint.
export function chatWSURL(): string {
  const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
  return `${proto}//${window.location.host}/api/chat/ws`;
}

export function cardChatWSURL(cardId: string): string {
  const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
  return `${proto}//${window.location.host}/api/cards/${cardId}/chat/ws`;
}

export function listNotifications(unreadOnly = false): Promise<Notification[]> {
  return fetchJSON(`/notifications${unreadOnly ? "?unread=1" : ""}`);
}

export function unreadNotificationCount(): Promise<{ count: number }> {
  return fetchJSON("/notifications/unread-count");
}

export function markNotificationRead(id: string): Promise<void> {
  return fetchJSON(`/notifications/${id}/read`, { method: "POST" });
}

export function markAllNotificationsRead(): Promise<{ count: number }> {
  return fetchJSON("/notifications/read-all", { method: "POST" });
}

export function listModules(): Promise<KanbanModule[]> {
  return fetchJSON("/modules");
}

export interface ModuleInput {
  name: string;
  kind: string;
  enabled: boolean;
  event_filter: string[];
  config: Record<string, unknown>;
}

export function createModule(body: ModuleInput): Promise<KanbanModule> {
  return fetchJSON("/modules", { method: "POST", body: JSON.stringify(body) });
}

export function updateModule(id: string, patch: Partial<ModuleInput>): Promise<KanbanModule> {
  return fetchJSON(`/modules/${id}`, { method: "PATCH", body: JSON.stringify(patch) });
}

export function deleteModule(id: string): Promise<void> {
  return fetchJSON(`/modules/${id}`, { method: "DELETE" });
}

export function listModuleLogs(id: string, limit = 100): Promise<ModuleLog[]> {
  return fetchJSON(`/modules/${id}/logs?limit=${limit}`);
}

export function testModule(idOrDraft: string, body?: ModuleInput): Promise<ModuleTestResult> {
  const init: RequestInit = { method: "POST" };
  if (body) init.body = JSON.stringify(body);
  return fetchJSON(`/modules/${idOrDraft}/test`, init);
}

// streamChat opens a WebSocket, sends the message history, and streams events
// to onEvent. Returns a Promise that resolves when the server closes the
// connection (after a "done" event) and rejects on transport errors.
export function streamChat(
  messages: ChatMessage[],
  onEvent: (ev: ChatStreamEvent) => void,
  signal?: AbortSignal
): Promise<void> {
  return new Promise((resolve, reject) => {
    const ws = new WebSocket(chatWSURL());
    let settled = false;
    const finish = (err?: Error) => {
      if (settled) return;
      settled = true;
      try {
        ws.close();
      } catch {
        /* ignore */
      }
      if (err) reject(err);
      else resolve();
    };

    if (signal) {
      const abort = () => finish(new Error("aborted"));
      if (signal.aborted) {
        abort();
        return;
      }
      signal.addEventListener("abort", abort, { once: true });
    }

    ws.onopen = () => {
      ws.send(JSON.stringify({ messages }));
    };
    ws.onmessage = (e) => {
      try {
        const ev = JSON.parse(typeof e.data === "string" ? e.data : "") as ChatStreamEvent;
        onEvent(ev);
        if (ev.type === "done" || ev.type === "error") {
          finish(ev.type === "error" ? new Error(ev.error) : undefined);
        }
      } catch (err) {
        finish(err as Error);
      }
    };
    ws.onerror = () => finish(new Error("websocket error"));
    ws.onclose = () => finish();
  });
}

export function login(username: string, password: string): Promise<User> {
  return fetchJSON("/auth/login", {
    method: "POST",
    body: JSON.stringify({ username, password }),
  });
}

export function register(username: string, password: string, display_name?: string): Promise<User> {
  return fetchJSON("/auth/register", {
    method: "POST",
    body: JSON.stringify({ username, password, display_name }),
  });
}

export function logout(): Promise<void> {
  return fetchJSON("/auth/logout", { method: "POST" });
}

export function getMe(): Promise<User> {
  return fetchJSON("/me");
}

export function updateMe(patch: { color?: string }): Promise<User> {
  return fetchJSON("/me", { method: "PATCH", body: JSON.stringify(patch) });
}

export function listUsers(): Promise<User[]> {
  return fetchJSON("/users");
}

export function listTags(): Promise<string[]> {
  return fetchJSON("/tags");
}

export function listRequesters(): Promise<string[]> {
  return fetchJSON("/requesters");
}

// --- Files (issue 0128) -----------------------------------------------------

export function listCardFiles(cardId: string): Promise<CardFile[]> {
  return fetchJSON(`/cards/${cardId}/files`);
}

export async function uploadCardFile(
  cardId: string,
  file: File,
  source: "upload" | "description" | "chat" = "upload"
): Promise<CardFile> {
  const fd = new FormData();
  fd.append("file", file);
  fd.append("source", source);
  const res = await fetch(`${BASE}/cards/${cardId}/files`, {
    method: "POST",
    credentials: "same-origin",
    body: fd,
  });
  if (!res.ok) {
    let msg = `upload failed: ${res.status}`;
    try {
      const body = (await res.json()) as { Message?: string; message?: string };
      if (body.Message || body.message) msg = body.Message || body.message || msg;
    } catch {
      /* ignore */
    }
    throw new HTTPError(res.status, msg);
  }
  return (await res.json()) as CardFile;
}

export function deleteCardFile(fileId: string): Promise<void> {
  return fetchJSON(`/files/${fileId}`, { method: "DELETE" });
}

// --- MCP per-user tokens ----------------------------------------------------

export interface MCPToken {
  id: string;
  name: string;
  created_at: string;
  last_used_at?: string;
}

export interface MCPTokenCreated extends MCPToken {
  token: string;
}

export function createMCPToken(name: string): Promise<MCPTokenCreated> {
  return fetchJSON("/mcp-tokens", { method: "POST", body: JSON.stringify({ name }) });
}

export function listMCPTokens(): Promise<MCPToken[]> {
  return fetchJSON("/mcp-tokens");
}

export function revokeMCPToken(id: string): Promise<void> {
  return fetchJSON(`/mcp-tokens/${id}`, { method: "DELETE" });
}

// --- Jira sync state + import ----------------------------------------------

export interface CardJiraSyncState {
  card_id: string;
  jira_key: string;
  last_status: string;
  last_sync_at: string;
  last_error: string;
  inflight: boolean;
  issue_url?: string;
}

export function getCardJiraSync(cardId: string): Promise<CardJiraSyncState> {
  return fetchJSON(`/cards/${cardId}/jira-sync`);
}

export interface JiraIssue {
  key: string;
  summary: string;
  status_name: string;
  issue_type: string;
  assignee: string;
  updated: string;
  url: string;
  already_imported: boolean;
  mapped_column_id?: string;
  issue_type_icon?: string;
}

export interface ListJiraIssuesResponse {
  issues: JiraIssue[];
  board_id: number;
  project_key: string;
  status_to_column: Record<string, string>;
  include_imported: boolean;
}

export function listJiraIssues(opts?: { includeImported?: boolean; limit?: number }): Promise<ListJiraIssuesResponse> {
  const qs = new URLSearchParams();
  if (opts?.includeImported) qs.set("include_imported", "true");
  if (opts?.limit) qs.set("limit", String(opts.limit));
  const q = qs.toString();
  return fetchJSON(`/jira/issues${q ? `?${q}` : ""}`);
}

export interface JiraImportResult {
  key: string;
  status: "imported" | "skipped" | "error";
  card_id?: string;
  column_id?: string;
  error?: string;
}

export function importJiraIssues(issueKeys: string[], fallbackColumnId?: string): Promise<{ results: JiraImportResult[] }> {
  return fetchJSON("/jira/import", {
    method: "POST",
    body: JSON.stringify({ issue_keys: issueKeys, fallback_column_id: fallbackColumnId || "" }),
  });
}

export function getMetrics(f: MetricsFilter): Promise<Metrics> {
  const qs = new URLSearchParams();
  if (f.from) qs.set("from", f.from);
  if (f.to) qs.set("to", f.to);
  if (f.assignee_id) qs.set("assignee_id", f.assignee_id);
  if (f.requester) qs.set("requester", f.requester);
  if (f.tags && f.tags.length > 0) qs.set("tags", f.tags.join(","));
  const q = qs.toString();
  return fetchJSON(`/metrics${q ? `?${q}` : ""}`);
}
