/**
 * Google Calendar / Tasks / People (Contacts) two-way sync helpers.
 *
 * Used by MailxService to push local edits to Google and pull server
 * changes into the local cache. All functions take a `getToken` function
 * and a fetch implementation so they stay platform-agnostic (Node uses
 * global `fetch` on Node 18+; browsers use window.fetch).
 *
 * Error handling: throws on network / HTTP errors. Caller catches and
 * either retries via the store_sync drainer or surfaces to the UI.
 */

type TokenProvider = () => Promise<string>;

export class GoogleHttpError extends Error {
    status: number;
    statusText: string;
    body: string;
    constructor(status: number, statusText: string, body: string) {
        super(`Google API ${status} ${statusText}: ${body.slice(0, 300)}`);
        this.status = status;
        this.statusText = statusText;
        this.body = body;
    }
}

async function googleFetch(
    tokenProvider: TokenProvider, url: string, init: RequestInit = {},
): Promise<Response> {
    const token = await tokenProvider();
    const headers = new Headers(init.headers || {});
    headers.set("Authorization", `Bearer ${token}`);
    if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
    const res = await fetch(url, { ...init, headers });
    if (!res.ok) {
        const body = await res.text().catch(() => "");
        throw new GoogleHttpError(res.status, res.statusText, body);
    }
    return res;
}

// ── Calendar ──

export interface GCalEvent {
    id: string;
    summary: string;
    start: { dateTime?: string; date?: string };
    end: { dateTime?: string; date?: string };
    location?: string;
    description?: string;
    etag?: string;
    /** Set on instances expanded from a recurring series (singleEvents=true).
     *  Absent for singletons. Lets the UI filter out recurring expansions. */
    recurringEventId?: string;
    /** Link to open the event in Google Calendar web. */
    htmlLink?: string;
}

export async function listCalendarEvents(
    tokenProvider: TokenProvider, fromMs: number, toMs: number, calendarId = "primary",
): Promise<GCalEvent[]> {
    const from = new Date(fromMs).toISOString();
    const to = new Date(toMs).toISOString();
    const url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events`
        + `?timeMin=${encodeURIComponent(from)}&timeMax=${encodeURIComponent(to)}`
        + `&singleEvents=true&orderBy=startTime&maxResults=250`;
    const all: GCalEvent[] = [];
    let pageToken: string | undefined;
    do {
        const pagedUrl = pageToken ? `${url}&pageToken=${encodeURIComponent(pageToken)}` : url;
        const res = await googleFetch(tokenProvider, pagedUrl);
        const data = await res.json() as any;
        for (const ev of (data.items || [])) all.push(ev);
        pageToken = data.nextPageToken;
    } while (pageToken);
    return all;
}

export async function createCalendarEvent(
    tokenProvider: TokenProvider, event: any, calendarId = "primary",
): Promise<GCalEvent> {
    const url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events`;
    const res = await googleFetch(tokenProvider, url, { method: "POST", body: JSON.stringify(event) });
    return res.json();
}

export async function updateCalendarEvent(
    tokenProvider: TokenProvider, eventId: string, event: any, calendarId = "primary",
): Promise<GCalEvent> {
    const url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
    const res = await googleFetch(tokenProvider, url, { method: "PATCH", body: JSON.stringify(event) });
    return res.json();
}

export async function deleteCalendarEvent(
    tokenProvider: TokenProvider, eventId: string, calendarId = "primary",
): Promise<void> {
    const url = `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}`;
    await googleFetch(tokenProvider, url, { method: "DELETE" });
}

// ── Tasks ──

export interface GTask {
    id: string;
    title: string;
    notes?: string;
    due?: string;
    completed?: string;
    status?: "needsAction" | "completed";
    etag?: string;
}

export async function listTasks(
    tokenProvider: TokenProvider, listId = "@default", showCompleted = false,
): Promise<GTask[]> {
    const url = `https://tasks.googleapis.com/tasks/v1/lists/${encodeURIComponent(listId)}/tasks`
        + `?showCompleted=${showCompleted}&maxResults=100`;
    const res = await googleFetch(tokenProvider, url);
    const data = await res.json() as any;
    return data.items || [];
}

export async function createTask(
    tokenProvider: TokenProvider, task: any, listId = "@default",
): Promise<GTask> {
    const url = `https://tasks.googleapis.com/tasks/v1/lists/${encodeURIComponent(listId)}/tasks`;
    const res = await googleFetch(tokenProvider, url, { method: "POST", body: JSON.stringify(task) });
    return res.json();
}

export async function updateTask(
    tokenProvider: TokenProvider, taskId: string, task: any, listId = "@default",
): Promise<GTask> {
    const url = `https://tasks.googleapis.com/tasks/v1/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}`;
    const res = await googleFetch(tokenProvider, url, { method: "PATCH", body: JSON.stringify(task) });
    return res.json();
}

export async function deleteTask(
    tokenProvider: TokenProvider, taskId: string, listId = "@default",
): Promise<void> {
    const url = `https://tasks.googleapis.com/tasks/v1/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}`;
    await googleFetch(tokenProvider, url, { method: "DELETE" });
}

// ── Contacts (People API) ──

export async function createContact(
    tokenProvider: TokenProvider, person: any,
): Promise<any> {
    const url = `https://people.googleapis.com/v1/people:createContact`;
    const res = await googleFetch(tokenProvider, url, { method: "POST", body: JSON.stringify(person) });
    return res.json();
}

export async function updateContact(
    tokenProvider: TokenProvider, resourceName: string, updatePersonFields: string, person: any,
): Promise<any> {
    const url = `https://people.googleapis.com/v1/${resourceName}:updateContact`
        + `?updatePersonFields=${encodeURIComponent(updatePersonFields)}`;
    const res = await googleFetch(tokenProvider, url, { method: "PATCH", body: JSON.stringify(person) });
    return res.json();
}

export async function deleteContact(
    tokenProvider: TokenProvider, resourceName: string,
): Promise<void> {
    const url = `https://people.googleapis.com/v1/${resourceName}:deleteContact`;
    await googleFetch(tokenProvider, url, { method: "DELETE" });
}

// ── Conversion helpers ──

export function calendarEventToLocal(ev: GCalEvent, accountId: string): {
    providerId: string; accountId: string; title: string;
    startMs: number; endMs: number; allDay: boolean;
    location: string; notes: string; etag: string;
    recurringEventId: string | undefined;
    htmlLink: string | undefined;
} {
    const allDay = !!ev.start.date && !ev.start.dateTime;
    const startMs = allDay
        ? Date.parse(ev.start.date!)
        : Date.parse(ev.start.dateTime!);
    const endMs = allDay
        ? Date.parse(ev.end.date!)
        : Date.parse(ev.end.dateTime!);
    return {
        providerId: ev.id, accountId,
        title: ev.summary || "(no title)",
        startMs, endMs, allDay,
        location: ev.location || "", notes: ev.description || "",
        etag: ev.etag || "",
        recurringEventId: ev.recurringEventId,
        htmlLink: ev.htmlLink,
    };
}

export function localToCalendarEvent(local: {
    title: string; startMs: number; endMs: number; allDay?: boolean;
    location?: string; notes?: string;
}): any {
    const toIso = (ms: number) => new Date(ms).toISOString();
    const toDate = (ms: number) => new Date(ms).toISOString().slice(0, 10);
    return {
        summary: local.title,
        start: local.allDay ? { date: toDate(local.startMs) } : { dateTime: toIso(local.startMs) },
        end: local.allDay ? { date: toDate(local.endMs) } : { dateTime: toIso(local.endMs) },
        location: local.location || undefined,
        description: local.notes || undefined,
    };
}

export function taskToLocal(t: GTask, accountId: string): {
    providerId: string; accountId: string; title: string; notes: string;
    dueMs: number | undefined; completedMs: number | undefined; etag: string;
} {
    return {
        providerId: t.id, accountId, title: t.title || "",
        notes: t.notes || "",
        dueMs: t.due ? Date.parse(t.due) : undefined,
        completedMs: t.completed ? Date.parse(t.completed) : undefined,
        etag: t.etag || "",
    };
}

export function localToTask(local: {
    title: string; notes?: string; dueMs?: number; completedMs?: number;
}): any {
    return {
        title: local.title,
        notes: local.notes || undefined,
        due: local.dueMs ? new Date(local.dueMs).toISOString() : undefined,
        completed: local.completedMs ? new Date(local.completedMs).toISOString() : undefined,
        status: local.completedMs ? "completed" : "needsAction",
    };
}
