/**
 * SSO auto-login page for Mi Aplicación.
 * The Hub App Launcher redirects here with ?relay=<token> after generating a relay token.
 * This server-side load function exchanges the relay token for KC tokens via the
 * Hub backend, sets HttpOnly cookies, and redirects to /dashboard.
 */
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { setAccessTokenCookies } from '$lib/server/access-token-cookie';
import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth';
import { isValidSystem, setActiveSystemCookie } from '$lib/server/system-gate';

// Disable client-side rendering to prevent SvelteKit from making a second
// __data.json request that would consume the one-time relay token twice.
export const csr = false;

export const load: PageServerLoad = async ({ url, cookies }) => {
	const relayToken = url.searchParams.get('relay');
	const requestedSystem = url.searchParams.get('active_system');
	console.log('[SSO] relay token presente:', !!relayToken, '| active_system:', requestedSystem ?? '(none)');

	if (!relayToken) {
		redirectToWorkspaceLogin(cookies, url);
	}

	// Limpiar sesión anterior para que el nuevo usuario reciba sus propias cookies.
	// No se omite el exchange aunque haya token existente — la sesión podría ser
	// de otro usuario (ej: juan que hace logout e ingresa como lal17).
	// La única excepción es si el relay token ya fue consumido (lo maneja el error handler).
	{
		const { clearAccessTokenCookies } = await import('$lib/server/access-token-cookie');
		clearAccessTokenCookies(cookies);
		cookies.delete('refresh_token', { path: '/' });
		cookies.delete('id_token', { path: '/' });
		cookies.delete('active_company_id', { path: '/' });
	}

	// SSO exchange must call the Hub that GENERATED the relay token.
	// This fetch runs server-side (inside the Docker container), so we must use
	// INTERNAL_HUB_URL (host.docker.internal) when available — "localhost" inside
	// a container never reaches the host where the workspace Hub is running.
	const hubUrl = (
		process.env.INTERNAL_HUB_URL ||
		process.env.HUB_URL ||
		process.env.VITE_HUB_URL ||
		'http://localhost:8001'
	).replace(/\/+$/, '');
	const baseUrl = `${hubUrl}/`;

	let response: Response;
	try {
		response = await fetch(`${baseUrl}api/v1/auth/sso-exchange`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ relay_token: relayToken }),
		});
	} catch (err) {
		redirectToWorkspaceLogin(cookies, url);
	}

	if (!response.ok) {
		const body = await response.json().catch(() => ({}));
		const detail: string = body?.detail || 'sso_exchange_failed';
		console.error('[SSO] exchange falló:', response.status, detail);

		// If the token is "invalid/used", a concurrent request may have already
		// succeeded and set cookies. Redirect to /dashboard — if the session is
		// valid it will load; if not, the dashboard layout will redirect to /login.
		const tokenAlreadyUsed =
			detail.toLowerCase().includes('inválido') ||
			detail.toLowerCase().includes('invalido') ||
			detail.toLowerCase().includes('invalid') ||
			detail.toLowerCase().includes('used') ||
			detail.toLowerCase().includes('expired');
		if (tokenAlreadyUsed) {
			throw redirect(303, '/dashboard');
		}

		redirectToWorkspaceLogin(cookies, url);
	}

	let tokens: Record<string, unknown>;
	try {
		tokens = await response.json();
	} catch (err) {
		console.error('[SSO] exchange devolvió body no-JSON (status 200):', err);
		redirectToWorkspaceLogin(cookies, url);
	}

	if (!tokens.access_token || typeof tokens.access_token !== 'string') {
		console.error('[SSO] exchange exitoso pero access_token faltante o inválido:', tokens);
		redirectToWorkspaceLogin(cookies, url);
	}

	console.log('[SSO] exchange exitoso, tokens recibidos:', {
		hasAccessToken: !!tokens.access_token,
		accessTokenLen: (tokens.access_token as string).length,
		hasRefreshToken: !!tokens.refresh_token,
		tenant_id: tokens.tenant_id,
		tenant_slug: tokens.tenant_slug,
	});

	// ── Refresh proactivo ────────────────────────────────────────────────────
	// Los tokens del relay fueron emitidos por KC via el browser (iss=IP:8085).
	// El Hub backend valida contra KC interno (hub-keycloak:8080) → issuer mismatch → 401.
	// Refrescando aquí: Mi Aplicación backend → Hub → KC interno → iss=hub-keycloak:8080 → válido.
	if (typeof tokens.refresh_token === 'string') {
		try {
			const internalApiUrl = (
				process.env.INTERNAL_API_URL ||
				process.env.VITE_API_URL ||
				'http://backend:8000/api/'
			).replace(/\/+$/, '');
			const refreshRes = await fetch(`${internalApiUrl}/v1/auth/refresh`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ refresh_token: tokens.refresh_token }),
			});
			if (refreshRes.ok) {
				const refreshed = await refreshRes.json().catch(() => ({}));
				if (refreshed.access_token && refreshed.refresh_token) {
					tokens = { ...tokens, ...refreshed };
					console.log('[SSO] tokens refrescados exitosamente (iss normalizado)');
				}
			} else {
				console.warn('[SSO] refresh proactivo falló (status', refreshRes.status, ') — usando tokens originales del relay');
			}
		} catch (err) {
			console.warn('[SSO] refresh proactivo error (non-blocking):', err);
		}
	}

	const { isSecureContext } = await import('$lib/server/workspace-auth');
	const isProduction = isSecureContext();
	console.log('[SSO] ORIGIN-based secure context:', isProduction);

	// Sesión local del CRM (patrón SIWEB): si el refresh proactivo devolvió una
	// sesión local firmada, el access_token guarda ESA sesión (bearer de la app,
	// sobrevive aunque el refresh KC del Hub falle) y el token KC va a su propia
	// cookie kc_access_token (solo para llamadas directas al Hub). Si no vino
	// (flag apagado), comportamiento histórico: access_token = token KC.
	const sessionToken = typeof tokens.session_token === 'string' ? tokens.session_token : null;
	const kcAccessToken = tokens.access_token as string;

	// access_token — NO HttpOnly (Bearer desde JS); fragmentado si el JWT supera ~4KB
	setAccessTokenCookies(cookies, sessionToken ?? kcAccessToken, {
		secure: isProduction,
		maxAge: 60 * 60 * 24 * 7,
	});

	if (sessionToken) {
		// kc_access_token — HttpOnly; solo el server lo usa para llamar al Hub.
		cookies.set('kc_access_token', kcAccessToken, {
			path: '/', httpOnly: true, secure: isProduction, sameSite: 'lax', maxAge: 60 * 60 * 24 * 7,
		});
		if (typeof tokens.session_id === 'string') {
			cookies.set('crm_sid', tokens.session_id, {
				path: '/', httpOnly: true, secure: isProduction, sameSite: 'lax', maxAge: 60 * 60 * 24 * 30,
			});
		}
	}

	// refresh_token — HttpOnly (never exposed to JS)
	if (typeof tokens.refresh_token === 'string') {
		cookies.set('refresh_token', tokens.refresh_token, {
			path: '/',
			httpOnly: true,
			secure: isProduction,
			sameSite: 'lax',
			maxAge: 60 * 60 * 24 * 30,
		});
	}

	// id_token — requerido para id_token_hint en el logout de Keycloak.
	// Puede venir del refresh proactivo o del exchange original.
	if (typeof tokens.id_token === 'string') {
		cookies.set('id_token', tokens.id_token, {
			path: '/',
			httpOnly: true,
			secure: isProduction,
			sameSite: 'lax',
			maxAge: 60 * 60 * 24 * 7,
		});
	}

	// sso_tenant_id — HttpOnly cookie con el tenant seleccionado.
	// El backend lo pasa como X-Tenant-Override en Hub /auth/me para que
	// devuelva el tenant correcto aunque el KC token tenga otro tenant baked in.
	if (typeof tokens.tenant_id === 'number') {
		cookies.set('sso_tenant_id', String(tokens.tenant_id), {
			path: '/',
			httpOnly: true,
			secure: isProduction,
			sameSite: 'lax',
			maxAge: 60 * 60 * 24 * 7,
		});
		// sso_tenant_pub — companion no-HttpOnly para que el cliente JS pueda
		// leer el tenant override e incluirlo como X-Tenant-Override en fetch directo al backend.
		// No es un secreto (solo un ID numérico; Hub valida UserTenant en cada request).
		cookies.set('sso_tenant_pub', String(tokens.tenant_id), {
			path: '/',
			httpOnly: false,
			secure: isProduction,
			sameSite: 'lax',
			maxAge: 60 * 60 * 24 * 7,
		});
	}
	console.log('[SSO] cookies configuradas, preparando redirect a /dashboard con active_system:', requestedSystem ?? '(none)');

	// Ejecutar lazy-link server-side: crear UserTenant si hay invite pendiente.
	// Se llama con el Bearer token recién obtenido. Best-effort, no bloquea el SSO.
	try {
		const internalApiUrl = (
			process.env.INTERNAL_API_URL ||
			process.env.VITE_API_URL ||
			'http://backend:8000/api/'
		).replace(/\/+$/, '');
		await fetch(`${internalApiUrl}/v1/auth/lazy-link`, {
			method: 'POST',
			headers: {
				'Authorization': `Bearer ${tokens.access_token as string}`,
				'Content-Type': 'application/json',
			},
		}).catch(() => {});
	} catch { /* non-blocking */ }

	if (isValidSystem(requestedSystem)) {
		setActiveSystemCookie(cookies, requestedSystem);
	}

	throw redirect(303, '/dashboard');
};
