// Shared HTTP client for the native (pre-WebView) server-mode auth flow.
//
// One place for: credentials mode, request timeouts, CSRF (double-submit token
// fetched from a readable endpoint and echoed as a header), and error
// normalization into user-facing messages. CapacitorHttp (enabled globally in
// capacitor.config.ts) routes fetch() through native HTTP so these calls aren't
// blocked by CORS and the session cookie lands in the WebView's cookie store.

import { REQUEST_TIMEOUT_MS, CSRF_HEADER, CSRF_TOKEN_PATH } from './config';

export interface ApiResult<T> {
  ok: boolean;
  /** HTTP status, or 0 for a network/timeout failure before any response. */
  status: number;
  data: T | null;
  /** Normalized, user-facing message when !ok; null on success. */
  error: string | null;
}

export interface LoginResponse {
  requires_totp?: boolean;
  requires_webauthn?: boolean;
  challenge_token?: string;
  user?: { id: number; username: string; role: string };
}

interface ApiErrorBody {
  message?: string;
  error?: string;
  code?: string;
}

/** Map a failed response to a single honest, user-facing sentence. */
function messageForStatus(status: number, body: ApiErrorBody | null): string {
  const serverMsg = body?.message || body?.error;
  switch (true) {
    case status === 401:
    case status === 403:
      return serverMsg || 'Not authorized. Please sign in again.';
    case status === 429:
      return serverMsg || 'Too many attempts. Please wait a moment and try again.';
    case status >= 500:
      return 'The server had a problem. Please try again in a moment.';
    default:
      return serverMsg || `Request failed (${status}).`;
  }
}

async function request<T>(
  url: string,
  init: RequestInit,
  timeoutMs = REQUEST_TIMEOUT_MS,
): Promise<ApiResult<T>> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const res = await fetch(url, {
      ...init,
      credentials: 'include',
      signal: controller.signal,
    });
    let data: T | null = null;
    try {
      data = (await res.json()) as T;
    } catch {
      data = null; // empty or non-JSON body
    }
    if (!res.ok) {
      return { ok: false, status: res.status, data, error: messageForStatus(res.status, data as ApiErrorBody | null) };
    }
    return { ok: true, status: res.status, data, error: null };
  } catch (err) {
    const aborted = err instanceof DOMException ? err.name === 'AbortError' : (err as { name?: string })?.name === 'AbortError';
    return {
      ok: false,
      status: 0,
      data: null,
      error: aborted
        ? 'The server took too long to respond. Check your connection and try again.'
        : 'Could not reach the server. Check the URL and your connection.',
    };
  } finally {
    clearTimeout(timer);
  }
}

function postJson<T>(url: string, body: unknown, headers: Record<string, string> = {}): Promise<ApiResult<T>> {
  return request<T>(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...headers },
    body: JSON.stringify(body),
  });
}

/** POST /api/auth/login — CSRF-exempt (no session yet). */
export function login(baseUrl: string, username: string, password: string): Promise<ApiResult<LoginResponse>> {
  return postJson<LoginResponse>(`${baseUrl}/api/auth/login`, { username, password });
}

/**
 * Fetch the double-submit CSRF token from the readable endpoint. Returns '' on
 * failure (the caller surfaces the downstream 403 rather than blocking here).
 */
export async function fetchCsrfToken(baseUrl: string): Promise<string> {
  const res = await request<{ token?: string }>(`${baseUrl}${CSRF_TOKEN_PATH}`, { method: 'GET' });
  return res.ok && res.data?.token ? res.data.token : '';
}

/**
 * POST /api/auth/totp/challenge — NOT CSRF-exempt, so fetch + echo the token.
 */
export async function totpChallenge(
  baseUrl: string,
  challengeToken: string,
  code: string,
): Promise<ApiResult<LoginResponse>> {
  const csrf = await fetchCsrfToken(baseUrl);
  return postJson<LoginResponse>(
    `${baseUrl}/api/auth/totp/challenge`,
    { challenge_token: challengeToken, code },
    csrf ? { [CSRF_HEADER]: csrf } : {},
  );
}

export interface SessionUser {
  id: number;
  username: string;
  role: string;
  name?: string;
}

/** GET /api/auth/me — current session; 401 when not signed in. */
export function getSession(baseUrl: string): Promise<ApiResult<{ user?: SessionUser } & Partial<SessionUser>>> {
  return request(`${baseUrl}/api/auth/me`, { method: 'GET' });
}

// ── Domain: the monthly Tracker (subset of /api/tracker we render) ────────────

export interface Bill {
  id: number;
  name: string;
  category_name: string | null;
  due_date: string; // YYYY-MM-DD
  due_day: number;
  bucket: string;
  expected_amount: number;
  balance: number;
  total_paid: number;
  status: string; // 'paid' | 'missed' | 'upcoming' | 'partial' | 'autodraft' | …
  is_settled: boolean;
  has_payment: boolean;
  autopay_enabled: boolean;
}

export interface TrackerSummary {
  total_expected: number;
  total_paid: number;
  remaining: number;
  total_remaining: number;
  overdue: number;
  count_paid: number;
  count_upcoming: number;
  count_late: number;
  remaining_label: string;
}

export interface TrackerData {
  year: number;
  month: number; // 1–12
  today: string;
  summary: TrackerSummary;
  rows: Bill[];
}

/** Authed mutation: fetch the CSRF token, then echo it as x-csrf-token. */
async function mutate<T>(baseUrl: string, method: string, path: string, body?: unknown): Promise<ApiResult<T>> {
  const csrf = await fetchCsrfToken(baseUrl);
  return request<T>(`${baseUrl}${path}`, {
    method,
    headers: { 'Content-Type': 'application/json', ...(csrf ? { [CSRF_HEADER]: csrf } : {}) },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
}

/** GET /api/tracker — a month's bills + summary (defaults to the current month). */
export function getTracker(baseUrl: string, year?: number, month?: number): Promise<ApiResult<TrackerData>> {
  const q = year && month ? `?year=${year}&month=${month}` : '';
  return request<TrackerData>(`${baseUrl}/api/tracker${q}`, { method: 'GET' });
}

/** POST /api/bills/:id/toggle-paid — flip a bill's paid state for the month. */
export function toggleBillPaid(baseUrl: string, billId: number): Promise<ApiResult<unknown>> {
  return mutate(baseUrl, 'POST', `/api/bills/${billId}/toggle-paid`, {});
}

/** POST /api/bills/ — create a bill. */
export function createBill(
  baseUrl: string,
  bill: { name: string; due_day: number; expected_amount: number },
): Promise<ApiResult<Bill>> {
  return mutate<Bill>(baseUrl, 'POST', '/api/bills/', bill);
}

/** POST /api/auth/logout */
export function logout(baseUrl: string): Promise<ApiResult<unknown>> {
  return mutate(baseUrl, 'POST', '/api/auth/logout', {});
}
