/**
 * Utilidades para llamadas a la API desde el servidor (SSR)
 * Centraliza la lógica de configuración de URL, autenticación y manejo de tokens
 */

import { redirect, type Cookies } from '@sveltejs/kit';
import {
	clearAccessTokenCookies,
	getAccessTokenFromCookies,
	setAccessTokenCookies
} from '$lib/server/access-token-cookie';

/**
 * Obtiene y normaliza la URL base de la API para llamadas desde el servidor
 * Automáticamente reemplaza localhost/127.0.0.1 con 'backend' para Docker
 */
export function getServerApiUrl(): string {
	// Primero intentar con INTERNAL_API_URL (para llamadas server-side en Docker)
	let apiUrl = process.env.INTERNAL_API_URL;

	// Si no está definida, usar VITE_API_URL del entorno runtime (no import.meta.env)
	if (!apiUrl) {
		apiUrl = process.env.VITE_API_URL;
	}

	// Como último recurso, usar el valor de build-time
	if (!apiUrl) {
		apiUrl = import.meta.env.VITE_API_URL;
		// Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR)
		apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend').replace('anexo76-dev.aduanasoft.com', 'backend');
	}

	// Normalizar la URL: asegurar que termine con '/'
	return apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
}

/**
 * Obtiene los tokens de autenticación de las cookies
 */
export function getAuthTokens(cookies: Cookies) {
	return {
		accessToken: getAccessTokenFromCookies(cookies),
		refreshToken: cookies.get('refresh_token')
	};
}

/**
 * Establece los tokens de autenticación en las cookies
 *
 * Política de seguridad:
 *  - access_token  → NO HttpOnly (Bearer desde JS); si el JWT es muy grande, varias cookies fragmentadas
 *  - refresh_token → HttpOnly=true (JS nunca lo lee; el servidor lo maneja via /api-sveltekit/auth/silent-refresh)
 */
export function setAuthTokens(
	cookies: Cookies,
	accessToken: string,
	refreshToken?: string
) {
	setAccessTokenCookies(cookies, accessToken, {
		secure: process.env.NODE_ENV === 'production',
		maxAge: 60 * 60 * 24 * 7 // 7 días
	});

	if (refreshToken) {
		cookies.set('refresh_token', refreshToken, {
			path: '/',
			httpOnly: true,    // *** HttpOnly: JS nunca lee el refresh_token ***
			sameSite: 'lax',
			secure: process.env.NODE_ENV === 'production',
			maxAge: 60 * 60 * 24 * 30 // 30 días
		});
	}
}

/**
 * Limpia todos los tokens de autenticación de las cookies
 */
export function clearAuthTokens(cookies: Cookies) {
	clearAccessTokenCookies(cookies);
	cookies.delete('refresh_token', { path: '/' });
	cookies.delete('active_company_id', { path: '/' });
}

/**
 * Crea headers de autorización con el token Bearer
 */
export function createAuthHeaders(token: string, additionalHeaders?: Record<string, string>, tenantOverride?: string) {
	return {
		'Authorization': `Bearer ${token}`,
		'Content-Type': 'application/json',
		...(tenantOverride ? { 'X-Tenant-Override': tenantOverride } : {}),
		...additionalHeaders
	};
}

/**
 * Intenta refrescar el token de acceso usando el refresh token
 * @returns El nuevo access token o null si falla
 */
export async function refreshAccessToken(
	cookies: Cookies,
	fetch: typeof globalThis.fetch
): Promise<string | null> {
	const { refreshToken } = getAuthTokens(cookies);

	if (!refreshToken) {
		return null;
	}

	try {
		const baseUrl = getServerApiUrl();
		const response = await fetch(`${baseUrl}v1/auth/refresh`, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({ refresh_token: refreshToken })
		});

		if (!response.ok) {
			return null;
		}

		const data = await response.json();

		// Actualizar las cookies con los nuevos tokens
		setAuthTokens(cookies, data.access_token, data.refresh_token);

		return data.access_token;
	} catch (error) {
		console.error('🔄 [API] Error al refrescar token:', error);
		return null;
	}
}

/**
 * Realiza una petición autenticada a la API con manejo automático de refresh
 * @param endpoint - Endpoint relativo (ej: 'v1/auth/me')
 * @param options - Opciones de fetch
 * @param cookies - Objeto de cookies de SvelteKit
 * @param fetch - Función fetch de SvelteKit
 * @param redirectUrl - URL a la que redirigir si falla la autenticación (opcional)
 * @param timeout - Timeout en milisegundos (default: 30000ms)
 */
export async function authenticatedFetch(
	endpoint: string,
	options: RequestInit = {},
	cookies: Cookies,
	fetch: typeof globalThis.fetch,
	redirectUrl?: string,
	timeout: number = 30000
): Promise<Response> {
	try {
		const baseUrl = getServerApiUrl();
		let { accessToken } = getAuthTokens(cookies);

		// Si no hay token, redirigir o lanzar error
		if (!accessToken) {
			if (redirectUrl) {
				throw redirect(303, redirectUrl);
			}
			throw new Error('No access token available');
		}

		// Construir URL completa
		const url = endpoint.startsWith('http') ? endpoint : `${baseUrl}${endpoint}`;

		// Leer tenant override de cookie SSO (flujo multi-tenant relay)
		const tenantOverride = cookies.get('sso_tenant_id');

		// Crear AbortController para timeout
		const controller = new AbortController();
		const timeoutId = setTimeout(() => {
			console.error(`⏱️ [API] Timeout después de ${timeout}ms:`, endpoint);
			controller.abort();
		}, timeout);

		// Realizar la petición inicial
		// Si el body es FormData, no incluir Content-Type (el navegador lo establece con el boundary)
		const isFormData = options.body instanceof FormData;
		const headers = isFormData
			? { 'Authorization': `Bearer ${accessToken}`, ...(options.headers as Record<string, string> || {}) }
			: createAuthHeaders(accessToken, options.headers as Record<string, string>, tenantOverride);

		let response = await fetch(url, {
			...options,
			headers,
			signal: controller.signal
		});

		clearTimeout(timeoutId);

		// Si es 403, no intentar refrescar - es un problema de permisos
		if (response.status === 403) {
			console.warn('🚫 [API] Acceso denegado (403):', endpoint);
			return response; // Retornar directamente para que el llamador maneje el error
		}

		// Si es 401, intentar refrescar el token
		if (response.status === 401) {
			const newToken = await refreshAccessToken(cookies, fetch);

			if (newToken) {
				// Reintentar la petición con el nuevo token
				const newController = new AbortController();
				const newTimeoutId = setTimeout(() => {
					console.error(`⏱️ [API] Timeout en retry después de ${timeout}ms:`, endpoint);
					newController.abort();
				}, timeout);

				// Si el body es FormData, no incluir Content-Type
				const newHeaders = isFormData
					? { 'Authorization': `Bearer ${newToken}`, ...(options.headers as Record<string, string> || {}) }
					: createAuthHeaders(newToken, options.headers as Record<string, string>, tenantOverride);

				response = await fetch(url, {
					...options,
					headers: newHeaders,
					signal: newController.signal
				});

				clearTimeout(newTimeoutId);
			} else {
				// No se pudo refrescar, limpiar y redirigir
				clearAuthTokens(cookies);
				if (redirectUrl) {
					throw redirect(303, redirectUrl);
				}
			}
		}

		return response;
	} catch (error) {
		// Si es un redirect, re-lanzarlo
		if (error && typeof error === 'object' && 'status' in error && 'location' in error) {
			throw error;
		}

		console.error('🔴 [API] Error en authenticatedFetch:', endpoint, error);

		// Retornar una respuesta de error simulada en lugar de lanzar
		return new Response(JSON.stringify({ error: 'Network error', details: String(error) }), {
			status: 500,
			headers: { 'Content-Type': 'application/json' }
		});
	}
}

/**
 * Valida que el usuario esté autenticado y obtiene sus datos
 * @returns Los datos del usuario o null si no está autenticado
 */
export async function validateAuth(
	cookies: Cookies,
	fetch: typeof globalThis.fetch,
	redirectOnFail?: string
): Promise<any> {
	const pickAvatar = (...candidates: Array<unknown>): string | null => {
		for (const candidate of candidates) {
			if (typeof candidate === 'string' && candidate.trim().length > 0) {
				return candidate.trim();
			}
		}
		return null;
	};

	try {
		const response = await authenticatedFetch(
			'v1/auth/me',
			{},
			cookies,
			fetch,
			redirectOnFail
		);

		if (!response.ok) {
			if (redirectOnFail) {
				clearAuthTokens(cookies);
				throw redirect(303, redirectOnFail);
			}
			return null;
		}

		const keycloakData = await response.json();
		const workspaceAvatarFromAuthMe = pickAvatar(
			keycloakData.avatar_url,
			keycloakData.avatarUrl,
			keycloakData.picture,
			keycloakData.photo
		);
		console.debug('[avatar][validateAuth] /v1/auth/me avatar_url recibido:', workspaceAvatarFromAuthMe ?? '(null)');

		// Obtener perfil adicional del usuario (avatar, bio, etc.)
		try {
			const profileResponse = await authenticatedFetch(
				'v1/core/users/me/profile',
				{},
				cookies,
				fetch
			);

			if (profileResponse.ok) {
				const profileData = await profileResponse.json();
				const workspaceAvatarFromProfile = pickAvatar(
					profileData.workspaceAvatarUrl,
					profileData.workspace_avatar_url
				);
				const legacyAvatar = pickAvatar(
					profileData.legacyAvatarUrl,
					profileData.legacy_avatar_url,
					profileData.avatarUrl,
					profileData.avatar_url,
					profileData.avatar,
					profileData.photo,
					profileData.picture
				);
				const finalWorkspaceAvatar = pickAvatar(workspaceAvatarFromAuthMe, workspaceAvatarFromProfile);
				const finalAvatar = pickAvatar(finalWorkspaceAvatar, legacyAvatar);
				console.debug('[avatar][validateAuth] avatar final resuelto:', finalAvatar ?? '(null)');

				// Combinar datos de Keycloak con datos del perfil.
				// Prioridad para nombre: caché local del perfil > JWT claims.
				return {
					...keycloakData,
					id: profileData.id || keycloakData.id || keycloakData.sub,
					username: profileData.username || keycloakData.username || keycloakData.preferred_username || '',
					email: profileData.email || keycloakData.email || '',
					first_name: profileData.first_name || keycloakData.first_name || keycloakData.given_name || '',
					last_name: profileData.last_name || keycloakData.last_name || keycloakData.family_name || '',
					avatar_url: finalAvatar,
					avatarUrl: finalAvatar,
					workspace_avatar_url: finalWorkspaceAvatar,
					workspaceAvatarUrl: finalWorkspaceAvatar,
					legacy_avatar_url: legacyAvatar,
					legacyAvatarUrl: legacyAvatar,
					phone: profileData.phone || null,
					bio: profileData.bio || null,
					preferences: profileData.preferences || {}
				};
			}
		} catch (profileError) {
			console.warn('⚠️ [API] No se pudo cargar el perfil del usuario, usando solo datos de Keycloak');
		}

		// Fallback: map raw JWT claim names to the expected field names
		const nameParts = (keycloakData.name || '').split(' ');
		const finalAvatar = workspaceAvatarFromAuthMe;
		console.debug('[avatar][validateAuth] fallback auth/me avatar final:', finalAvatar ?? '(null)');
		return {
			...keycloakData,
			id: keycloakData.id || keycloakData.sub,
			username: keycloakData.username || keycloakData.preferred_username || '',
			first_name: keycloakData.first_name || keycloakData.given_name || nameParts[0] || '',
			last_name: keycloakData.last_name || keycloakData.family_name || nameParts.slice(1).join(' ') || '',
			avatar_url: finalAvatar,
			avatarUrl: finalAvatar,
			workspace_avatar_url: finalAvatar,
			workspaceAvatarUrl: finalAvatar,
		};
	} catch (error) {
		// Si es un redirect, re-lanzarlo
		if (error && typeof error === 'object' && 'status' in error && 'location' in error) {
			throw error;
		}

		console.error('🔐 [API] Error validando autenticación:', error);

		if (redirectOnFail) {
			clearAuthTokens(cookies);
			throw redirect(303, redirectOnFail);
		}

		return null;
	}
}

/**
 * Obtiene las compañías del usuario autenticado
 */
export async function getUserCompanies(
	cookies: Cookies,
	fetch: typeof globalThis.fetch
): Promise<any[]> {
	try {
		const response = await authenticatedFetch(
			'v1/a76/company/my-companies',
			{},
			cookies,
			fetch
		);

		if (!response.ok) {
			console.error('🏢 [API] Error cargando compañías:', response.status);
			return [];
		}

		return await response.json();
	} catch (error) {
		console.error('🏢 [API] Error cargando compañías:', error);
		return [];
	}
}

/**
 * Obtiene el ID de la compañía activa, o la primera disponible si no hay ninguna seleccionada
 */
export async function getActiveCompanyId(
	cookies: Cookies,
	fetch: typeof globalThis.fetch
): Promise<string | null> {
	let companyId = cookies.get('active_company_id');

	// Si no hay companyId en cookie, obtener las compañías del usuario y usar la primera
	if (!companyId) {
		const companies = await getUserCompanies(cookies, fetch);
		if (companies.length > 0) {
			companyId = companies[0].id.toString();
		}
	}

	return companyId || null;
}

/**
 * Helper para manejar respuestas de API y convertir errores 403 en formato adecuado
 * para mostrar toasts en el cliente
 */
export async function handleApiResponse<T = any>(
	response: Response
): Promise<{ data?: T; error?: { detail: string; status: number; isForbidden?: boolean } }> {
	if (response.ok) {
		// Para respuestas sin contenido (204)
		if (response.status === 204) {
			return { data: null as T };
		}

		const data = await response.json();
		return { data };
	}

	// Manejar errores
	const errorData = await response.json().catch(() => ({ detail: 'Error desconocido' }));

	const error = {
		detail: errorData.detail || errorData.message || 'Error en la petición',
		status: response.status,
		isForbidden: response.status === 403
	};

	return { error };
}
