import { ProcessedReceipt } from "@/lib/schema/receipt";
import { apiFetch } from "@/lib/csrf/client";
import { MAX_RECEIPTS_JSON_BYTES } from "@/lib/limits";

/**
 * Server-backed receipt storage (PostgreSQL).
 *
 * All receipts live in PostgreSQL. List fetches omit preview images; open a
 * receipt to load the preview. If the database is unavailable the helpers
 * degrade to empty results rather than throwing.
 */

/** Guest receipts land under one shared bucket when no account is signed in. */
export const GUEST_USER_ID = "guest_default";

async function sessionUserId(): Promise<string | null> {
  try {
    const res = await apiFetch("/api/auth/session", { cache: "no-store" });
    if (!res.ok) return null;
    const data = await res.json();
    return typeof data?.user?.id === "string" ? data.user.id : null;
  } catch {
    return null;
  }
}

/** The id to scope server receipts to — the signed-in account or the guest bucket. */
export async function getCurrentUserId(): Promise<string> {
  return (await sessionUserId()) ?? GUEST_USER_ID;
}

export async function fetchServerReceipts(userId: string): Promise<ProcessedReceipt[]> {
  try {
    const res = await apiFetch(`/api/receipts?userId=${encodeURIComponent(userId)}`, {
      cache: "no-store",
    });
    if (!res.ok) return [];
    const data = await res.json();
    return Array.isArray(data.receipts) ? (data.receipts as ProcessedReceipt[]) : [];
  } catch {
    return [];
  }
}

/** Single receipt including the preview image (list fetches omit it). */
export async function fetchServerReceipt(id: string): Promise<ProcessedReceipt | null> {
  try {
    const res = await apiFetch(
      `/api/receipts?id=${encodeURIComponent(id)}&includePreview=1`,
      { cache: "no-store" }
    );
    if (!res.ok) return null;
    const data = await res.json();
    if (data.receipt) return data.receipt as ProcessedReceipt;
    if (Array.isArray(data.receipts) && data.receipts[0]) {
      return data.receipts[0] as ProcessedReceipt;
    }
    return null;
  } catch {
    return null;
  }
}

export interface SaveReceiptsResult {
  ok: boolean;
  status?: number;
  error?: string;
}

const RECEIPTS_POST_BUDGET = MAX_RECEIPTS_JSON_BYTES - 64 * 1024;

function chunkReceiptsForPost(receipts: ProcessedReceipt[], userId: string): ProcessedReceipt[][] {
  if (receipts.length <= 1) return receipts.length === 0 ? [] : [receipts];

  const chunks: ProcessedReceipt[][] = [];
  let current: ProcessedReceipt[] = [];
  let currentSize = JSON.stringify({ userId, receipts: [] }).length;

  for (const receipt of receipts) {
    const encodedSize = JSON.stringify(receipt).length + 1;
    if (current.length > 0 && currentSize + encodedSize > RECEIPTS_POST_BUDGET) {
      chunks.push(current);
      current = [];
      currentSize = JSON.stringify({ userId, receipts: [] }).length;
    }
    current.push(receipt);
    currentSize += encodedSize;
  }
  if (current.length > 0) chunks.push(current);
  return chunks;
}

async function parseSaveError(res: Response): Promise<string> {
  try {
    const data = (await res.json()) as { error?: unknown; message?: unknown };
    if (typeof data.error === "string" && data.error.length > 0) return data.error;
    if (typeof data.message === "string" && data.message.length > 0) return data.message;
  } catch {
    // body wasn't JSON
  }
  if (res.status === 413) return "request_too_large";
  if (res.status === 503) return "database_unavailable";
  return `http_${res.status}`;
}

export function saveFailureMessage(result: SaveReceiptsResult, isDe: boolean): string {
  const code = result.error ?? "";
  if (code === "network") {
    return isDe
      ? "Keine Verbindung zum Server. Prüfe dein Netz und versuche es erneut."
      : "No connection to the server. Check your network and try again.";
  }
  if (code === "request_too_large" || result.status === 413) {
    return isDe
      ? "Der Beleg ist zum Speichern zu groß. Bitte versuche es erneut oder lade ein kleineres Foto hoch."
      : "This receipt is too large to save. Try again or upload a smaller photo.";
  }
  if (code === "database_unavailable" || result.status === 503) {
    return isDe
      ? "Die Datenbank ist gerade nicht erreichbar. Der Scan bleibt in dieser Sitzung, geht beim Neuladen aber verloren."
      : "The database is unreachable. The scan stays in this session but will be lost on reload.";
  }
  if (code.startsWith("http_")) {
    return isDe
      ? `Speichern fehlgeschlagen (HTTP ${result.status ?? code.slice(5)}).`
      : `Save failed (HTTP ${result.status ?? code.slice(5)}).`;
  }
  if (code) return code;
  return isDe ? "Speichern auf dem Server fehlgeschlagen." : "Failed to save to the server.";
}

export async function saveServerReceipts(
  receipts: ProcessedReceipt[],
  userId: string
): Promise<SaveReceiptsResult> {
  if (receipts.length === 0) return { ok: true };

  const chunks = chunkReceiptsForPost(receipts, userId);
  try {
    for (const chunk of chunks) {
      const res = await apiFetch("/api/receipts", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ userId, receipts: chunk }),
      });
      if (!res.ok) {
        return { ok: false, status: res.status, error: await parseSaveError(res) };
      }
    }
    return { ok: true };
  } catch {
    return {
      ok: false,
      error: "network",
    };
  }
}

export async function deleteServerReceipt(id: string): Promise<boolean> {
  try {
    const res = await apiFetch(`/api/receipts?id=${encodeURIComponent(id)}`, { method: "DELETE" });
    return res.ok;
  } catch {
    return false;
  }
}

export async function deleteServerReceipts(ids: string[]): Promise<void> {
  await Promise.all(ids.map((id) => deleteServerReceipt(id)));
}

/**
 * Pro-only receipt "folders". Empty results / no-ops on failure mirror the
 * degrade-gracefully behavior of the receipt helpers above — a free-plan
 * user simply sees an empty project list rather than an error.
 */
export interface ServerProject {
  id: string;
  name: string;
  color: string | null;
  receiptCount: number;
  createdAt: string;
  updatedAt: string;
}

export async function fetchProjects(): Promise<ServerProject[]> {
  try {
    const res = await apiFetch("/api/projects", { cache: "no-store" });
    if (!res.ok) return [];
    const data = await res.json();
    return Array.isArray(data.projects) ? (data.projects as ServerProject[]) : [];
  } catch {
    return [];
  }
}

export async function fetchProject(id: string): Promise<ServerProject | null> {
  try {
    const res = await apiFetch(`/api/projects/${encodeURIComponent(id)}`, { cache: "no-store" });
    if (!res.ok) return null;
    const data = await res.json();
    return data.project ?? null;
  } catch {
    return null;
  }
}

export async function createProject(
  name: string,
  color?: string | null
): Promise<ServerProject | null> {
  try {
    const res = await apiFetch("/api/projects", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name, color: color ?? null }),
    });
    if (!res.ok) return null;
    const data = await res.json();
    return data.project ?? null;
  } catch {
    return null;
  }
}

export async function renameProject(
  id: string,
  updates: { name?: string; color?: string | null }
): Promise<ServerProject | null> {
  try {
    const res = await apiFetch(`/api/projects/${encodeURIComponent(id)}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(updates),
    });
    if (!res.ok) return null;
    const data = await res.json();
    return data.project ?? null;
  } catch {
    return null;
  }
}

export async function deleteProject(id: string): Promise<boolean> {
  try {
    const res = await apiFetch(`/api/projects/${encodeURIComponent(id)}`, { method: "DELETE" });
    return res.ok;
  } catch {
    return false;
  }
}

export async function assignReceiptsToProject(
  ids: string[],
  projectId: string | null
): Promise<boolean> {
  if (ids.length === 0) return true;
  try {
    const res = await apiFetch("/api/receipts/project", {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ ids, projectId }),
    });
    return res.ok;
  } catch {
    return false;
  }
}