/**
 * Google Calendar API client.
 *
 * Pure functions using native fetch — no googleapis library needed.
 * Handles token auto-refresh, calendar listing, event fetch (full + incremental),
 * event creation, and mapping Google events → rCal CalendarEvent fields.
 */

import { connectionsDocId } from '../modules/rdocs/schemas';
import type { ConnectionsDoc } from '../modules/rdocs/schemas';
import type { CalendarEvent } from '../modules/rcal/schemas';
import type { SyncServer } from './local-first/sync-server';

const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';

const GCAL_BASE = 'https://www.googleapis.com/calendar/v3';

// ── Types ──

export interface GoogleCalendarEntry {
	id: string;
	summary: string;
	description?: string;
	backgroundColor?: string;
	foregroundColor?: string;
	primary?: boolean;
	accessRole: string;
	selected?: boolean;
	timeZone?: string;
}

export interface GoogleEvent {
	id: string;
	status: string;
	summary?: string;
	description?: string;
	location?: string;
	start?: { dateTime?: string; date?: string; timeZone?: string };
	end?: { dateTime?: string; date?: string; timeZone?: string };
	recurrence?: string[];
	attendees?: Array<{ email: string; displayName?: string; responseStatus?: string; self?: boolean }>;
	hangoutLink?: string;
	conferenceData?: { entryPoints?: Array<{ entryPointType: string; uri: string; label?: string }> };
	htmlLink?: string;
	updated?: string;
}

export interface GoogleEventInput {
	summary: string;
	description?: string;
	location?: string;
	start: { dateTime?: string; date?: string; timeZone?: string };
	end: { dateTime?: string; date?: string; timeZone?: string };
}

export interface FetchEventsResult {
	events: GoogleEvent[];
	nextSyncToken: string | null;
	deleted: string[];
}

// ── Token management ──

/**
 * Get a valid Google access token for a space, auto-refreshing if expired.
 * Returns null if no Google connection exists.
 */
export async function getValidGoogleToken(
	space: string,
	syncServer: SyncServer,
): Promise<string | null> {
	const docId = connectionsDocId(space);
	const doc = syncServer.getDoc<ConnectionsDoc>(docId);
	if (!doc?.google?.refreshToken) return null;

	// Check if token is still valid (with 60s buffer)
	if (doc.google.accessToken && doc.google.expiresAt > Date.now() + 60_000) {
		return doc.google.accessToken;
	}

	// Refresh the token
	const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
		method: 'POST',
		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
		body: new URLSearchParams({
			client_id: GOOGLE_CLIENT_ID,
			client_secret: GOOGLE_CLIENT_SECRET,
			refresh_token: doc.google.refreshToken,
			grant_type: 'refresh_token',
		}),
	});

	if (!tokenRes.ok) {
		const err = await tokenRes.text();
		throw new Error(`Google token refresh failed: ${err}`);
	}

	const tokenData = (await tokenRes.json()) as any;

	syncServer.changeDoc<ConnectionsDoc>(docId, 'Auto-refresh Google token', (d) => {
		if (d.google) {
			d.google.accessToken = tokenData.access_token;
			d.google.expiresAt = Date.now() + (tokenData.expires_in || 3600) * 1000;
		}
	});

	return tokenData.access_token;
}

// ── API helpers ──

async function gcalFetch(url: string, token: string, opts: RequestInit = {}): Promise<any> {
	const res = await fetch(url, {
		...opts,
		headers: {
			Authorization: `Bearer ${token}`,
			'Content-Type': 'application/json',
			...((opts.headers as Record<string, string>) || {}),
		},
	});

	if (!res.ok) {
		const body = await res.text();
		const err = new Error(`Google Calendar API error ${res.status}: ${body}`) as any;
		err.status = res.status;
		throw err;
	}

	return res.json();
}

// ── Calendar listing ──

/** List all calendars the user has access to. */
export async function listGoogleCalendars(token: string): Promise<GoogleCalendarEntry[]> {
	const data = await gcalFetch(`${GCAL_BASE}/users/me/calendarList`, token);
	return (data.items || []) as GoogleCalendarEntry[];
}

// ── Event fetching (full + incremental) ──

/**
 * Fetch events from a Google Calendar.
 *
 * - Without syncToken: full fetch (uses timeMin/timeMax window)
 * - With syncToken: incremental fetch (only changes since last sync)
 *
 * Returns events, deleted event IDs, and the next sync token.
 */
export async function fetchGoogleEvents(
	token: string,
	calendarId: string,
	opts: {
		syncToken?: string | null;
		timeMin?: string;
		timeMax?: string;
		maxResults?: number;
	} = {},
): Promise<FetchEventsResult> {
	const allEvents: GoogleEvent[] = [];
	const deleted: string[] = [];
	let pageToken: string | undefined;
	let nextSyncToken: string | null = null;

	do {
		const params = new URLSearchParams({
			maxResults: String(opts.maxResults || 250),
			singleEvents: 'true',
		});

		if (opts.syncToken) {
			// Incremental sync — only pass syncToken, no time bounds
			params.set('syncToken', opts.syncToken);
		} else {
			// Full sync — use time window
			if (opts.timeMin) params.set('timeMin', opts.timeMin);
			if (opts.timeMax) params.set('timeMax', opts.timeMax);
			params.set('orderBy', 'startTime');
		}

		if (pageToken) params.set('pageToken', pageToken);

		const data = await gcalFetch(
			`${GCAL_BASE}/calendars/${encodeURIComponent(calendarId)}/events?${params}`,
			token,
		);

		for (const item of data.items || []) {
			if (item.status === 'cancelled') {
				deleted.push(item.id);
			} else {
				allEvents.push(item as GoogleEvent);
			}
		}

		pageToken = data.nextPageToken;
		if (data.nextSyncToken) nextSyncToken = data.nextSyncToken;
	} while (pageToken);

	return { events: allEvents, nextSyncToken, deleted };
}

// ── Event creation (manual push) ──

/** Create an event on a Google Calendar. Returns the created event's Google ID. */
export async function createGoogleEvent(
	token: string,
	calendarId: string,
	event: GoogleEventInput,
): Promise<string> {
	const data = await gcalFetch(
		`${GCAL_BASE}/calendars/${encodeURIComponent(calendarId)}/events`,
		token,
		{ method: 'POST', body: JSON.stringify(event) },
	);
	return data.id;
}

// ── Event mapping ──

/** Parse a Google Calendar date/dateTime field to epoch milliseconds. */
function parseGoogleDateTime(dt?: { dateTime?: string; date?: string }): { ms: number; allDay: boolean } {
	if (!dt) return { ms: 0, allDay: false };
	if (dt.dateTime) return { ms: new Date(dt.dateTime).getTime(), allDay: false };
	if (dt.date) return { ms: new Date(dt.date + 'T00:00:00').getTime(), allDay: true };
	return { ms: 0, allDay: false };
}

/** Extract virtual meeting URL from Google event. */
function extractVirtualUrl(gEvent: GoogleEvent): { url: string | null; platform: string | null } {
	if (gEvent.hangoutLink) return { url: gEvent.hangoutLink, platform: 'Google Meet' };
	const ep = gEvent.conferenceData?.entryPoints?.find((e) => e.entryPointType === 'video');
	if (ep?.uri) {
		let platform = 'Video Call';
		if (ep.uri.includes('zoom.us')) platform = 'Zoom';
		else if (ep.uri.includes('teams.microsoft')) platform = 'Microsoft Teams';
		else if (ep.uri.includes('meet.jit.si')) platform = 'Jitsi';
		return { url: ep.uri, platform };
	}
	return { url: null, platform: null };
}

/** Map a Google Calendar event to rCal CalendarEvent fields. */
export function mapGoogleEventToCalendar(
	gEvent: GoogleEvent,
	sourceId: string,
	sourceName: string,
	sourceColor: string | null,
): Partial<CalendarEvent> {
	const start = parseGoogleDateTime(gEvent.start);
	const end = parseGoogleDateTime(gEvent.end);
	const virtual = extractVirtualUrl(gEvent);

	const attendees = (gEvent.attendees || [])
		.filter((a) => !a.self)
		.map((a) => ({
			name: a.displayName || a.email,
			email: a.email,
			status: (
				a.responseStatus === 'accepted' ? 'yes'
					: a.responseStatus === 'declined' ? 'no'
						: a.responseStatus === 'tentative' ? 'maybe'
							: 'pending'
			) as 'yes' | 'no' | 'maybe' | 'pending',
			respondedAt: Date.now(),
			source: 'import' as const,
		}));

	return {
		title: gEvent.summary || '(No title)',
		description: gEvent.description || '',
		startTime: start.ms,
		endTime: end.ms || start.ms + 3600000,
		allDay: start.allDay,
		timezone: gEvent.start?.timeZone || null,
		rrule: gEvent.recurrence?.[0] || null,
		status: gEvent.status || null,
		likelihood: null,
		visibility: null,
		sourceId,
		sourceName,
		sourceType: 'GOOGLE',
		sourceColor,
		locationId: null,
		locationName: gEvent.location || null,
		coordinates: null,
		locationGranularity: null,
		locationLat: null,
		locationLng: null,
		locationBreadcrumb: null,
		bookingStatus: null,
		isVirtual: !!virtual.url,
		virtualUrl: virtual.url,
		virtualPlatform: virtual.platform,
		rToolSource: 'GOOGLE_CAL',
		rToolEntityId: gEvent.id,
		attendees,
		attendeeCount: attendees.length,
		tags: null,
		metadata: null,
	};
}

/** Convert an rCal event to Google Calendar event input format. */
export function mapCalendarEventToGoogle(event: CalendarEvent): GoogleEventInput {
	if (event.allDay) {
		const startDate = new Date(event.startTime).toISOString().slice(0, 10);
		const endDate = new Date(event.endTime || event.startTime + 86400000).toISOString().slice(0, 10);
		return {
			summary: event.title,
			description: event.description || undefined,
			location: event.locationName || undefined,
			start: { date: startDate },
			end: { date: endDate },
		};
	}

	return {
		summary: event.title,
		description: event.description || undefined,
		location: event.locationName || undefined,
		start: {
			dateTime: new Date(event.startTime).toISOString(),
			timeZone: event.timezone || undefined,
		},
		end: {
			dateTime: new Date(event.endTime || event.startTime + 3600000).toISOString(),
			timeZone: event.timezone || undefined,
		},
	};
}
