import { getApiBaseUrl } from '$lib/api';
import { goto } from '$app/navigation';

/**
 * Service d'authentification côté client.
 *
 * Sécurité :
 * - Access token stocké en mémoire uniquement (pas localStorage - vulnérable XSS)
 * - Refresh token en cookie HttpOnly (géré côté serveur)
 *
 * @see Story 1.4 - Connexion utilisateur
 */

/** Délai entre les tentatives de refresh lors de race conditions multi-onglets (ms) */
const REFRESH_RACE_RETRY_DELAY_MS = 100;

// État réactif de l'authentification
let accessToken = $state<string | null>(null);
let currentUserId = $state<string | null>(null);
let isRefreshing = $state(false);

// Callback to clear user-specific caches on logout
let onLogoutCallback: (() => void) | null = null;

/**
 * Parse JWT payload to extract claims.
 * Note: This does NOT validate the token - validation is done server-side.
 */
function parseJwtPayload(token: string): Record<string, unknown> | null {
	try {
		const parts = token.split('.');
		if (parts.length !== 3) return null;
		const payloadPart = parts[1];
		if (!payloadPart) return null;
		const decoded = atob(payloadPart.replace(/-/g, '+').replace(/_/g, '/'));
		return JSON.parse(decoded) as Record<string, unknown>;
	} catch {
		return null;
	}
}

/**
 * Extract user ID from JWT token.
 */
function extractUserId(token: string): string | null {
	const payload = parseJwtPayload(token);
	if (!payload) return null;
	// JWT 'user_id' claim contains the UUID (set by JwtPayloadEnricher)
	// Note: 'sub' contains the email (Lexik default), not the UUID
	const userId = payload['user_id'];
	return typeof userId === 'string' ? userId : null;
}

export interface LoginCredentials {
	email: string;
	password: string;
	captcha_token?: string | undefined;
}

export interface LoginResult {
	success: boolean;
	error?: {
		type: 'invalid_credentials' | 'rate_limited' | 'captcha_required' | 'captcha_invalid' | 'account_suspended' | 'unknown';
		message: string;
		retryAfter?: number | undefined;
		delay?: number | undefined;
		attempts?: number | undefined;
		captchaRequired?: boolean | undefined;
	};
}

export interface ApiError {
	type: string;
	title: string;
	status: number;
	detail: string;
	retryAfter?: number | undefined;
}

/**
 * Effectue une tentative de connexion.
 */
export async function login(credentials: LoginCredentials): Promise<LoginResult> {
	const apiUrl = getApiBaseUrl();

	try {
		const response = await fetch(`${apiUrl}/login`, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json',
			},
			body: JSON.stringify(credentials),
			credentials: 'include', // Important pour recevoir le cookie refresh_token
		});

		if (response.ok) {
			const data = await response.json();
			accessToken = data.token;
			currentUserId = extractUserId(data.token);

			return { success: true };
		}

		// Erreur
		const error = await response.json() as ApiError & {
			attempts?: number;
			captchaRequired?: boolean;
			delay?: number;
		};

		// IP bloquée (429)
		if (response.status === 429) {
			return {
				success: false,
				error: {
					type: 'rate_limited',
					message: error.detail,
					retryAfter: error.retryAfter,
				},
			};
		}

		// CAPTCHA requis (428)
		if (response.status === 428) {
			return {
				success: false,
				error: {
					type: 'captcha_required',
					message: error.detail,
					attempts: error.attempts,
					captchaRequired: true,
				},
			};
		}

		// Compte suspendu (403)
		if (response.status === 403 && error.type === '/errors/account-suspended') {
			return {
				success: false,
				error: {
					type: 'account_suspended',
					message: error.detail,
				},
			};
		}

		// CAPTCHA invalide (400)
		if (response.status === 400 && error.type === '/errors/captcha-invalid') {
			return {
				success: false,
				error: {
					type: 'captcha_invalid',
					message: error.detail,
					captchaRequired: true,
				},
			};
		}

		// Authentification échouée (401)
		return {
			success: false,
			error: {
				type: 'invalid_credentials',
				message: error.detail || 'Identifiants incorrects',
				attempts: error.attempts,
				delay: error.delay,
				captchaRequired: error.captchaRequired,
			},
		};
	} catch (error) {
		console.error('[auth] Login error:', error);
		return {
			success: false,
			error: {
				type: 'unknown',
				message: 'Erreur de connexion. Veuillez réessayer.',
			},
		};
	}
}

/**
 * Rafraîchit le token JWT via le refresh token (cookie).
 *
 * Gère le cas de race condition multi-onglets (409 Conflict) :
 * Si deux onglets tentent de rafraîchir simultanément, le second recevra
 * un 409 car le token a déjà été rotaté. Dans ce cas, on attend un court
 * instant et on réessaie car le cookie contient maintenant le nouveau token.
 */
export async function refreshToken(retryCount = 0): Promise<boolean> {
	if (isRefreshing && retryCount === 0) {
		// Déjà en cours de refresh, attendre
		return new Promise((resolve) => {
			const interval = setInterval(() => {
				if (!isRefreshing) {
					clearInterval(interval);
					resolve(accessToken !== null);
				}
			}, REFRESH_RACE_RETRY_DELAY_MS);
		});
	}

	if (retryCount === 0) {
		isRefreshing = true;
	}

	const apiUrl = getApiBaseUrl();

	try {
		const response = await fetch(`${apiUrl}/token/refresh`, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json',
			},
			body: '{}',
			credentials: 'include',
		});

		if (response.ok) {
			const data = await response.json();
			accessToken = data.token;
			currentUserId = extractUserId(data.token);
			return true;
		}

		// 409 Conflict = token déjà rotaté (race condition multi-onglets)
		// Attendre un court instant et réessayer avec le nouveau cookie
		if (response.status === 409 && retryCount < 2) {
			await new Promise((resolve) => setTimeout(resolve, REFRESH_RACE_RETRY_DELAY_MS));
			return refreshToken(retryCount + 1);
		}

		// Refresh échoué - token expiré ou replay détecté
		accessToken = null;
		currentUserId = null;
		return false;
	} catch (error) {
		console.error('[auth] Refresh token error:', error);
		accessToken = null;
		currentUserId = null;
		return false;
	} finally {
		if (retryCount === 0) {
			isRefreshing = false;
		}
	}
}

/**
 * Effectue une requête authentifiée.
 * Rafraîchit automatiquement le token si nécessaire.
 */
export async function authenticatedFetch(
	url: string,
	options: RequestInit = {},
): Promise<Response> {
	// Si pas de token, essayer de rafraîchir
	if (!accessToken) {
		const refreshed = await refreshToken();
		if (!refreshed) {
			// Rediriger vers login
			goto('/login');
			throw new Error('Not authenticated');
		}
	}

	// Ajouter le token à la requête
	const headers = new Headers(options.headers);
	headers.set('Authorization', `Bearer ${accessToken}`);

	const response = await fetch(url, {
		...options,
		headers,
		credentials: 'include',
	});

	// Si 401, essayer de rafraîchir et rejouer
	if (response.status === 401) {
		const refreshed = await refreshToken();
		if (refreshed) {
			headers.set('Authorization', `Bearer ${accessToken}`);
			return fetch(url, { ...options, headers, credentials: 'include' });
		}

		// Refresh échoué, rediriger vers login
		goto('/login');
		throw new Error('Session expired');
	}

	return response;
}

/**
 * Déconnexion.
 */
export async function logout(): Promise<void> {
	const apiUrl = getApiBaseUrl();

	try {
		await fetch(`${apiUrl}/token/logout`, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json',
			},
			body: '{}',
			credentials: 'include',
		});
	} catch (error) {
		// Log mais ne pas bloquer la déconnexion locale
		console.warn('[auth] Logout API error (continuing with local logout):', error);
	}

	// Clear user-specific caches before resetting state
	if (onLogoutCallback) {
		onLogoutCallback();
	}

	accessToken = null;
	currentUserId = null;
	goto('/login');
}

/**
 * Vérifie si l'utilisateur est authentifié.
 */
export function isAuthenticated(): boolean {
	return accessToken !== null;
}

/**
 * Retourne le token actuel (pour debug uniquement).
 */
export function getAccessToken(): string | null {
	return accessToken;
}

/**
 * Retourne l'ID de l'utilisateur authentifié.
 * Utilisé pour scoper les caches par utilisateur.
 */
export function getCurrentUserId(): string | null {
	return currentUserId;
}

/**
 * Register a callback to be called on logout.
 * Used to clear user-specific caches (e.g., sessions query cache).
 */
export function onLogout(callback: () => void): void {
	onLogoutCallback = callback;
}
