/**
 * Google OAuth2 flow with token refresh.
 *
 * GET  /authorize?space=X  → redirect to Google
 * GET  /callback           → exchange code, store tokens, redirect back
 * POST /disconnect?space=X → revoke token
 * POST /refresh?space=X    → refresh access token using refresh token
 */

import { Hono } from 'hono';
import * as Automerge from '@automerge/automerge';
import { connectionsDocId } from '../../modules/rdocs/schemas';
import type { ConnectionsDoc } from '../../modules/rdocs/schemas';
import type { SyncServer } from '../local-first/sync-server';

const googleOAuthRoutes = new Hono();

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

const SCOPES = [
	'https://www.googleapis.com/auth/documents',
	'https://www.googleapis.com/auth/drive.file',
	'https://www.googleapis.com/auth/userinfo.email',
	'https://www.googleapis.com/auth/calendar.readonly',
	'https://www.googleapis.com/auth/calendar.events',
].join(' ');

let _syncServer: SyncServer | null = null;

export function setGoogleOAuthSyncServer(ss: SyncServer) {
	_syncServer = ss;
}

function ensureConnectionsDoc(space: string): ConnectionsDoc {
	if (!_syncServer) throw new Error('SyncServer not initialized');
	const docId = connectionsDocId(space);
	let doc = _syncServer.getDoc<ConnectionsDoc>(docId);
	if (!doc) {
		doc = Automerge.change(Automerge.init<ConnectionsDoc>(), 'init connections', (d) => {
			d.meta = {
				module: 'notes',
				collection: 'connections',
				version: 1,
				spaceSlug: space,
				createdAt: Date.now(),
			};
		});
		_syncServer.setDoc(docId, doc);
	}
	return doc;
}

// GET /authorize — redirect to Google OAuth
googleOAuthRoutes.get('/authorize', (c) => {
	const space = c.req.query('space');
	if (!space) return c.json({ error: 'space query param required' }, 400);
	if (!GOOGLE_CLIENT_ID) return c.json({ error: 'Google OAuth not configured' }, 500);

	const returnTo = c.req.query('returnTo') || 'rdocs';
	const state = Buffer.from(JSON.stringify({ space, returnTo })).toString('base64url');
	const params = new URLSearchParams({
		client_id: GOOGLE_CLIENT_ID,
		redirect_uri: GOOGLE_REDIRECT_URI,
		response_type: 'code',
		scope: SCOPES,
		access_type: 'offline',
		prompt: 'consent',
		state,
	});

	return c.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params}`);
});

// GET /callback — exchange code for tokens
googleOAuthRoutes.get('/callback', async (c) => {
	const code = c.req.query('code');
	const stateParam = c.req.query('state');

	if (!code || !stateParam) return c.json({ error: 'Missing code or state' }, 400);

	let state: { space: string; returnTo?: string };
	try {
		state = JSON.parse(Buffer.from(stateParam, 'base64url').toString());
	} catch {
		return c.json({ error: 'Invalid state parameter' }, 400);
	}

	// Exchange code for tokens
	const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
		method: 'POST',
		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
		body: new URLSearchParams({
			code,
			client_id: GOOGLE_CLIENT_ID,
			client_secret: GOOGLE_CLIENT_SECRET,
			redirect_uri: GOOGLE_REDIRECT_URI,
			grant_type: 'authorization_code',
		}),
	});

	if (!tokenRes.ok) {
		const err = await tokenRes.text();
		return c.json({ error: `Token exchange failed: ${err}` }, 502);
	}

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

	// Get user email
	let email = '';
	try {
		const userRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
			headers: { 'Authorization': `Bearer ${tokenData.access_token}` },
		});
		if (userRes.ok) {
			const userData = await userRes.json() as any;
			email = userData.email || '';
		}
	} catch {
		// Non-critical
	}

	// Store tokens
	ensureConnectionsDoc(state.space);
	const docId = connectionsDocId(state.space);

	_syncServer!.changeDoc<ConnectionsDoc>(docId, 'Connect Google', (d) => {
		d.google = {
			refreshToken: tokenData.refresh_token || '',
			accessToken: tokenData.access_token,
			expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000,
			email,
			connectedAt: Date.now(),
		};
	});

	const module = state.returnTo || 'rdocs';
	const redirectUrl = c.get("isSubdomain") ? `/${module}?connected=google` : `/${state.space}/${module}?connected=google`;
	return c.redirect(redirectUrl);
});

// POST /disconnect — revoke and remove token
googleOAuthRoutes.post('/disconnect', async (c) => {
	const space = c.req.query('space');
	if (!space) return c.json({ error: 'space query param required' }, 400);

	const docId = connectionsDocId(space);
	const doc = _syncServer?.getDoc<ConnectionsDoc>(docId);

	if (doc?.google?.accessToken) {
		// Revoke token with Google
		try {
			await fetch(`https://oauth2.googleapis.com/revoke?token=${doc.google.accessToken}`, {
				method: 'POST',
			});
		} catch {
			// Best-effort revocation
		}

		_syncServer!.changeDoc<ConnectionsDoc>(docId, 'Disconnect Google', (d) => {
			delete d.google;
		});
	}

	return c.json({ ok: true });
});

// POST /refresh — refresh access token
googleOAuthRoutes.post('/refresh', async (c) => {
	const space = c.req.query('space');
	if (!space) return c.json({ error: 'space query param required' }, 400);

	const docId = connectionsDocId(space);
	const doc = _syncServer?.getDoc<ConnectionsDoc>(docId);

	if (!doc?.google?.refreshToken) {
		return c.json({ error: 'No Google refresh token available' }, 400);
	}

	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) {
		return c.json({ error: 'Token refresh failed' }, 502);
	}

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

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

	return c.json({ ok: true });
});

export { googleOAuthRoutes };
