import type {
  APIKey,
  APIKeyScopes,
  AuthResponse,
  Calendar,
  CalendarEvent,
  CalendarMember,
  CalendarSubscription,
  Contact,
  ListResponse,
  PageInfo,
  User,
} from "./types";

// In development, Vite proxies relative paths to VITE_API_URL (set in vite.config.ts).
// In production, if frontend and API are on different origins, set VITE_API_BASE to the full API URL.
// If on the same origin (e.g. API serves the static files), leave VITE_API_BASE unset.
const API_BASE = (import.meta.env.VITE_API_BASE || "").replace(/\/$/, "");

let accessToken: string | null = localStorage.getItem("access_token");
let refreshToken: string | null = localStorage.getItem("refresh_token");
let refreshPromise: Promise<void> | null = null;

function apiUrl(path: string): string {
  return API_BASE ? `${API_BASE}${path.startsWith("/") ? path : `/${path}`}` : path;
}

export function setTokens(access: string, refresh: string) {
  accessToken = access;
  refreshToken = refresh;
  localStorage.setItem("access_token", access);
  localStorage.setItem("refresh_token", refresh);
}

export function clearTokens() {
  accessToken = null;
  refreshToken = null;
  localStorage.removeItem("access_token");
  localStorage.removeItem("refresh_token");
}

export function getRefreshToken() {
  return refreshToken;
}

async function doRefresh(): Promise<void> {
  if (!refreshToken) throw new Error("No refresh token");
  const res = await fetch(apiUrl("/auth/refresh"), {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refresh_token: refreshToken }),
  });
  if (!res.ok) {
    clearTokens();
    window.dispatchEvent(new CustomEvent("auth:logout"));
    throw new Error("Refresh failed");
  }
  const data = await res.json();
  setTokens(data.access_token, data.refresh_token);
}

async function request<T>(
  path: string,
  options: RequestInit = {},
): Promise<T> {
  const headers: Record<string, string> = {
    ...(options.headers as Record<string, string>),
  };

  if (accessToken) {
    headers["Authorization"] = `Bearer ${accessToken}`;
  }
  if (!(options.body instanceof FormData)) {
    headers["Content-Type"] = "application/json";
  }

  let res = await fetch(apiUrl(path), { ...options, headers });

  if (res.status === 401 && refreshToken) {
    if (!refreshPromise) {
      refreshPromise = doRefresh().finally(() => {
        refreshPromise = null;
      });
    }
    await refreshPromise;
    headers["Authorization"] = `Bearer ${accessToken}`;
    res = await fetch(apiUrl(path), { ...options, headers });
  }

  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw { status: res.status, ...body };
  }

  if (res.headers.get("content-type")?.includes("text/calendar")) {
    return (await res.text()) as unknown as T;
  }

  return res.json();
}

export const api = {
  // Auth
  register: (email: string, password: string, timezone?: string) =>
    request<AuthResponse>("/auth/register", {
      method: "POST",
      body: JSON.stringify({ email, password, timezone }),
    }),

  login: (email: string, password: string) =>
    request<AuthResponse>("/auth/login", {
      method: "POST",
      body: JSON.stringify({ email, password }),
    }),

  logout: () =>
    request<{ ok: boolean }>("/auth/logout", {
      method: "POST",
      body: JSON.stringify({ refresh_token: refreshToken }),
    }),

  me: () => request<{ user: User }>("/auth/me"),

  updateMe: (data: Partial<User>) =>
    request<{ user: User }>("/users/me", {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  deleteMe: () =>
    request<{ ok: boolean }>("/users/me", { method: "DELETE" }),

  // API Keys
  listApiKeys: () =>
    request<{ items: APIKey[]; page: PageInfo }>("/api-keys"),

  createApiKey: (data: { name: string; scopes: APIKeyScopes }) =>
    request<APIKey>("/api-keys", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  revokeApiKey: (id: string) =>
    request<{ ok: boolean }>(`/api-keys/${id}`, { method: "DELETE" }),

  // Calendars
  listCalendars: () => request<ListResponse<Calendar>>("/calendars?limit=100"),

  createCalendar: (data: { name: string; color?: string }) =>
    request<{ calendar: Calendar }>("/calendars", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  getCalendar: (id: string) =>
    request<{ calendar: Calendar }>(`/calendars/${id}`),

  updateCalendar: (
    id: string,
    data: {
      name?: string;
      color?: string;
      is_public?: boolean;
      count_for_availability?: boolean;
      default_reminder_minutes?: number;
      sort_order?: number;
    },
  ) =>
    request<{ calendar: Calendar }>(`/calendars/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  deleteCalendar: (id: string) =>
    request<{ ok: boolean }>(`/calendars/${id}`, { method: "DELETE" }),

  shareCalendar: (
    id: string,
    data: { target: { email: string }; role: string },
  ) =>
    request<{ ok: boolean }>(`/calendars/${id}/share`, {
      method: "POST",
      body: JSON.stringify(data),
    }),

  listMembers: (id: string) =>
    request<ListResponse<CalendarMember>>(`/calendars/${id}/members`),

  removeMember: (calId: string, userId: string) =>
    request<{ ok: boolean }>(`/calendars/${calId}/members/${userId}`, {
      method: "DELETE",
    }),

  addCalendarFromUrl: (data: { url: string; name?: string; color?: string }) =>
    request<{ ok: boolean; calendar: Calendar; imported: { events: number }; source: string }>(
      "/calendars/add-from-url",
      { method: "POST", body: JSON.stringify(data) },
    ),

  importCalendarUrl: (data: { calendar_id: string; url: string }) =>
    request<{ ok: boolean; imported: { events: number }; source: string }>(
      "/calendars/import-url",
      { method: "POST", body: JSON.stringify(data) },
    ),

  listSubscriptions: (calId: string) =>
    request<{ items: CalendarSubscription[]; page: PageInfo }>(
      `/calendars/${calId}/subscriptions`,
    ),

  addSubscription: (
    calId: string,
    data: { url: string; sync_interval_minutes?: number },
  ) =>
    request<{ ok: boolean; imported: { events: number }; source: string }>(
      `/calendars/${calId}/subscriptions`,
      { method: "POST", body: JSON.stringify(data) },
    ),

  deleteSubscription: (calId: string, subId: string) =>
    request<{ ok: boolean }>(`/calendars/${calId}/subscriptions/${subId}`, {
      method: "DELETE",
    }),

  syncSubscription: (calId: string, subId: string) =>
    request<{ ok: boolean; imported: { events: number } }>(
      `/calendars/${calId}/subscriptions/${subId}/sync`,
      { method: "POST" },
    ),

  // Events
  listEvents: (params: {
    start: string;
    end: string;
    calendar_id?: string;
    search?: string;
  }) => {
    const sp = new URLSearchParams({ start: params.start, end: params.end });
    if (params.calendar_id) sp.set("calendar_id", params.calendar_id);
    if (params.search) sp.set("search", params.search);
    sp.set("limit", "500");
    return request<ListResponse<CalendarEvent>>(`/events?${sp}`);
  },

  createEvent: (data: {
    calendar_id: string;
    title: string;
    start_time: string;
    end_time: string;
    timezone: string;
    description?: string;
    location?: string;
    all_day?: boolean;
    recurrence_rule?: string;
    tags?: string[];
  }) =>
    request<{ event: CalendarEvent }>("/events", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  getEvent: (id: string) =>
    request<{ event: CalendarEvent }>(`/events/${id}`),

  updateEvent: (id: string, data: Partial<CalendarEvent>) =>
    request<{ event: CalendarEvent }>(`/events/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  deleteEvent: (id: string) =>
    request<{ ok: boolean }>(`/events/${id}`, { method: "DELETE" }),

  // Contacts
  listContacts: (search?: string) => {
    const sp = new URLSearchParams({ limit: "200" });
    if (search) sp.set("search", search);
    return request<ListResponse<Contact>>(`/contacts?${sp}`);
  },

  createContact: (data: Partial<Contact>) =>
    request<{ contact: Contact }>("/contacts", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  getContact: (id: string) =>
    request<{ contact: Contact }>(`/contacts/${id}`),

  updateContact: (id: string, data: Partial<Contact>) =>
    request<{ contact: Contact }>(`/contacts/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  deleteContact: (id: string) =>
    request<{ ok: boolean }>(`/contacts/${id}`, { method: "DELETE" }),
};
