/**
 * Servicio de autenticación con Keycloak
 *
 * Seguridad de tokens:
 *  - access_token  → en memoria (authStore) + cookies no-HttpOnly (una o varias si el JWT es grande)
 *  - refresh_token → cookie HttpOnly únicamente (JS nunca lo lee directamente)
 *  - El refresh se hace server-side via /api-sveltekit/auth/silent-refresh
 *  - NO se usa localStorage para tokens
 */

import Keycloak from 'keycloak-js';
import { writable, derived, get } from 'svelte/store';
import { browser } from '$app/environment';
import {
	clearAccessTokenOnDocument,
	getAccessTokenFromDocument,
	setAccessTokenOnDocument
} from '$lib/access-token-cookie-browser';

// ─────────────────────────────────────────────────────────
// Tipos
// ─────────────────────────────────────────────────────────

export interface User {
	id: string;
	username: string;
	email?: string;
	name?: string;
	firstName?: string | null;
	lastName?: string | null;
	displayName?: string | null;
	avatarUrl?: string | null;
	workspaceAvatarUrl?: string | null;
	legacyAvatarUrl?: string | null;
	tenantId?: number;
	roles: string[];
	permissions: string[];
	// Cache management
	profileSyncedAt?: number; // timestamp en ms para cache TTL
}

export interface AuthState {
	isAuthenticated: boolean;
	isLoading: boolean;
	user: User | null;
	token: string | null;
}

// ─────────────────────────────────────────────────────────
// Configuración de Keycloak
// ─────────────────────────────────────────────────────────

const keycloakConfig = {
	url: import.meta.env.VITE_KEYCLOAK_URL,
	realm: import.meta.env.VITE_KEYCLOAK_REALM,
	clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID
};

let keycloakInstance: Keycloak | null = null;
const AUTH_USER_SESSION_KEY = 'anexo76_auth_user_v1';

function pickAvatar(...candidates: Array<unknown>): string | null {
	for (const candidate of candidates) {
		if (typeof candidate === 'string' && candidate.trim().length > 0) {
			return candidate.trim();
		}
	}
	return null;
}

function pickText(...candidates: Array<unknown>): string | null {
	for (const candidate of candidates) {
		if (typeof candidate === 'string') {
			const value = candidate.trim();
			if (value.length > 0) {
				return value;
			}
		}
	}
	return null;
}

function readUserFromSession(): User | null {
	if (!browser) return null;
	try {
		const raw = sessionStorage.getItem(AUTH_USER_SESSION_KEY);
		if (!raw) return null;
		const parsed = JSON.parse(raw) as User;
		if (!parsed || typeof parsed !== 'object') return null;
		if (!parsed.id || !parsed.username) return null;
		return parsed;
	} catch {
		return null;
	}
}

function persistUserInSession(user: User | null): void {
	if (!browser) return;
	if (!user) {
		sessionStorage.removeItem(AUTH_USER_SESSION_KEY);
		return;
	}
	sessionStorage.setItem(AUTH_USER_SESSION_KEY, JSON.stringify(user));
}

// ─────────────────────────────────────────────────────────
// Auth store (tokens solo en memoria)
// ─────────────────────────────────────────────────────────

const createAuthStore = () => {
	const { subscribe, set, update } = writable<AuthState>({
		isAuthenticated: false,
		isLoading: true,
		user: null,
		token: null
	});

	return {
		subscribe,
		setAuthenticated: (authenticated: boolean) =>
			update((s) => ({ ...s, isAuthenticated: authenticated })),
		setLoading: (loading: boolean) =>
			update((s) => ({ ...s, isLoading: loading })),
		setUser: (user: User | null) => {
			persistUserInSession(user);
			update((s) => ({ ...s, user }));
		},
		setToken: (token: string | null) => update((s) => ({ ...s, token })),
		/** ⚠️  Los tokens ya NO se guardan en localStorage; solo en memoria. */
		setTokens: (accessToken: string, _refreshToken?: string) => {
			update((s) => ({ ...s, token: accessToken }));
			// El refresh_token llega en cookie HttpOnly desde el servidor;
			// el cliente no lo almacena ni lo lee en ningún momento.
		},
		reset: () =>
			{
				persistUserInSession(null);
				set({
					isAuthenticated: false,
					isLoading: false,
					user: null,
					token: null
				});
			}
	};
};

export const authStore = createAuthStore();

export const isAuthenticated = derived(authStore, ($a) => $a.isAuthenticated);
export const currentUser = derived(authStore, ($a) => $a.user);

/**
 * Verifica si el usuario tiene un permiso específico.
 * `user.permissions` debe incluir códigos de la app (p. ej. `user.view`); suelen
 * cargarse vía {@link syncCompanyPermissions}, no solo desde /auth/me del Hub.
 */
export function userHasPermission(user: User | null, permission: string): boolean {
	if (!user) return false;
	return user.roles.includes('admin') || user.permissions.includes(permission);
}

// ─────────────────────────────────────────────────────────
// Inicialización
// ─────────────────────────────────────────────────────────

/**
 * Inicializa el estado de autenticación en el cliente.
 * - Si hay un access_token en la cookie no-HttpOnly, lo usa.
 * - En cualquier caso intenta inicializar Keycloak JS (para el flujo SSO).
 */
export const initAuth = async (): Promise<boolean> => {
	if (!browser) return false;

	try {
		authStore.setLoading(true);

		const sessionUser = readUserFromSession();
		if (sessionUser) {
			authStore.setUser(sessionUser);
		}

		// Restaurar token desde cookie no-HttpOnly (password login flow)
		const cookieToken = getAccessTokenFromDocument();
		if (cookieToken) {
			authStore.setToken(cookieToken);
			authStore.setAuthenticated(true);
			await loadUserInfo(cookieToken).catch(() => { });
			authStore.setLoading(false);
			return true;
		}

		// Sin token local, intentar Keycloak JS (flujo SSO)
		const authenticated = await initKeycloak();
		authStore.setLoading(false);
		return authenticated;
	} catch (err) {
		console.error('[auth] Error en initAuth:', err);
		authStore.setLoading(false);
		return false;
	}
};

/** Inicializa Keycloak JS para el flujo SSO con PKCE */
export const initKeycloak = async (): Promise<boolean> => {
	if (!browser) return false;

	try {
		keycloakInstance = new Keycloak(keycloakConfig);

		const authenticated = await keycloakInstance.init({
			onLoad: 'check-sso',
			silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
			pkceMethod: 'S256',
			checkLoginIframe: false
		});

		if (authenticated) {
			await updateAuthState();
			setupKeycloakTokenHooks();
		}

		return authenticated;
	} catch (err) {
		console.error('[auth] Error inicializando Keycloak:', err);
		return false;
	}
};

let previousTenantId: number | undefined = undefined;

const updateAuthState = async () => {
	if (!keycloakInstance?.authenticated) {
		authStore.reset();
		persistUserInSession(null);
		return;
	}

	try {
		const profile = await keycloakInstance.loadUserProfile();
		const token = keycloakInstance.token ?? null;
		const parsed = keycloakInstance.tokenParsed as any;

		const roles: string[] = parsed?.realm_access?.roles ?? [];
		const tenantId: number | undefined = parsed?.tenant_id
			? parseInt(parsed.tenant_id)
			: undefined;

		const tenantChanged = previousTenantId !== undefined && previousTenantId !== tenantId;

		// Obtener permisos actuales para evitar que el SSO los borre si fallara el fetch posterior
		let currentPerms: string[] = [];
		try {
			const { get } = await import('svelte/store');
			const currentState = get(authStore);
			currentPerms = currentState.user?.permissions || [];
		} catch { }

		const previousUser = get(authStore).user;
		const firstName = pickText(profile.firstName, parsed?.given_name, previousUser?.firstName);
		const lastName = pickText(profile.lastName, parsed?.family_name, previousUser?.lastName);
		const fullNameFromParts = pickText(
			firstName && lastName ? `${firstName} ${lastName}` : null,
			firstName,
			lastName
		);
		const username = pickText(
			profile.username,
			parsed?.preferred_username,
			parsed?.username,
			previousUser?.username
		) ?? '';
		const name = pickText(
			profile.firstName || profile.lastName ? `${profile.firstName ?? ''} ${profile.lastName ?? ''}` : null,
			fullNameFromParts,
			parsed?.name,
			previousUser?.name,
			username
		) ?? username;

		const user: User = {
			id: pickText(profile.id, parsed?.sub, previousUser?.id) ?? '',
			username,
			email: pickText(profile.email, parsed?.email, previousUser?.email) ?? undefined,
			name,
			firstName,
			lastName,
			displayName: pickText(name, previousUser?.displayName, username),
			avatarUrl: pickAvatar(previousUser?.avatarUrl),
			workspaceAvatarUrl: pickAvatar(previousUser?.workspaceAvatarUrl),
			legacyAvatarUrl: pickAvatar(previousUser?.legacyAvatarUrl),
			tenantId,
			roles,
			permissions: parsed?.permissions?.length ? parsed.permissions : currentPerms,
			profileSyncedAt: previousUser?.profileSyncedAt
		};

		authStore.setAuthenticated(true);
		authStore.setUser(user);
		authStore.setToken(token);

		// ⚠️ IMPORTANTE: El SSO original de Keycloak no inyecta los permisos granulares
		// que viven en la base de datos de PostgreSQL en nuestro `permissions: parsed?.permissions`.
		// Necesitamos hacer polling a /auth/me para que `user.permissions` se rellene.
		if (token) {
			await loadUserInfo(token).catch(() => { });
		}

		if (tenantChanged && browser) {
			try {
				const { companyStore } = await import('./stores/company.svelte');
				companyStore.clear();
			} catch { }
		}

		previousTenantId = tenantId;
	} catch (err) {
		console.error('[auth] Error actualizando estado:', err);
		authStore.reset();
	}
};

// ─────────────────────────────────────────────────────────
// Keycloak JS token hooks (solo para el flujo SSO)
// ─────────────────────────────────────────────────────────

/**
 * Configura los callbacks de Keycloak JS para notificar al SessionManager
 * sobre cambios de token y eventos de sesión SSO.
 */
const setupKeycloakTokenHooks = () => {
	if (!keycloakInstance) return;

	keycloakInstance.onTokenExpired = () => {
		keycloakInstance
			?.updateToken(70)
			.then((refreshed) => {
				if (refreshed && keycloakInstance?.token) {
					authStore.setToken(keycloakInstance.token);
					import('./session-manager')
						.then(({ getSessionManager }) => {
							getSessionManager()?.updateToken(keycloakInstance!.token!);
						})
						.catch(() => { });
				}
			})
			.catch(() => {
				console.error('[auth] No se pudo refrescar el token de Keycloak');
				void logout();
			});
	};

	keycloakInstance.onAuthRefreshSuccess = () => {
		if (keycloakInstance?.token) authStore.setToken(keycloakInstance.token);
	};

	keycloakInstance.onAuthRefreshError = () => {
		console.error('[auth] Error en refresh de Keycloak — cerrando sesión');
		void logout();
	};

	keycloakInstance.onAuthLogout = () => {
		authStore.reset();
	};
};

// ─────────────────────────────────────────────────────────
// Login
// ─────────────────────────────────────────────────────────

/** Inicia sesión con Keycloak (OAuth redirect flow) */
export const loginWithKeycloak = async (tenantSlug?: string) => {
	if (!keycloakInstance) {
		console.error('[auth] Keycloak no está inicializado');
		return;
	}
	const options: any = { redirectUri: window.location.origin + '/callback' };
	if (tenantSlug) options.loginHint = tenantSlug;
	await keycloakInstance.login(options);
};

/**
 * Login con usuario/contraseña (legacy — el login principal es via form action del servidor).
 * Los tokens se guardan en cookies no-HttpOnly (fragmentadas si hace falta) y en memoria (authStore).
 * NO se guardan en localStorage.
 */
export const login = async (credentials: {
	username: string;
	password: string;
	tenant_slug: string;
}): Promise<{ success: boolean; error?: string; data?: any }> => {
	try {
		const { api } = await import('./api');
		const response = await api.auth.login(credentials);

		if (response.error) {
			return { success: false, error: response.error };
		}

		const loginData = response.data;
		if (loginData?.access_token) {
			// Guardar en memoria y en cookie no-HttpOnly para SSR
			authStore.setToken(loginData.access_token);
			authStore.setAuthenticated(true);
			setAccessTokenOnDocument(loginData.access_token);
			// El refresh_token llega en cookie HttpOnly desde el servidor.
			// NO lo guardamos en JS.
			await loadUserInfo(loginData.access_token);
		}

		return { success: true, data: loginData };
	} catch (err) {
		console.error('[auth] Error en login:', err);
		return { success: false, error: 'Error de conexión con el servidor' };
	}
};

// ─────────────────────────────────────────────────────────
// User info
// ─────────────────────────────────────────────────────────

/**
 * Permisos efectivos RBAC de la app para la compañía (backend: GET .../permissions/me).
 * Sin esto, `userHasPermission` solo ve lo que venga en /auth/me del Hub.
 */
export async function syncCompanyPermissions(companyId: number): Promise<void> {
	if (!browser || !Number.isFinite(companyId)) return;
	try {
		const { api } = await import('./api');
		const res = await api.get<{ permissions: string[] }>(
			`/v1/core/permissions/me?company_id=${companyId}`
		);
		if (res.error || res.data === undefined) return;
		const perms = res.data.permissions;
		if (!Array.isArray(perms)) return;
		const state = get(authStore);
		if (!state.user) return;
		authStore.setUser({
			...state.user,
			permissions: perms
		});
	} catch (e) {
		console.warn('[auth] syncCompanyPermissions:', e);
	}
}

export const refreshPermissions = async () => {
	const token = getToken();
	if (token) {
		await loadUserInfo(token);
		try {
			const { companyStore } = await import('./stores/company.svelte');
			const cid = companyStore.activeCompany?.id;
			if (cid) await syncCompanyPermissions(cid);
		} catch {
			// ignore
		}
		return true;
	}
	return false;
};

const loadUserInfo = async (token: string) => {
	try {
		authStore.setToken(token);
		const { api } = await import('./api');
		const response = await api.auth.me();
		if (response.data) {
			const previousUser = get(authStore).user;
			const d = response.data;
			const workspaceAvatarUrl = pickAvatar(
				d.workspaceAvatarUrl,
				d.workspace_avatar_url,
				d.avatar_url,
				d.avatarUrl,
				d.picture,
				d.photo,
				previousUser?.workspaceAvatarUrl
			);
			const legacyAvatarUrl = pickAvatar(
				d.legacyAvatarUrl,
				d.legacy_avatar_url,
				d.avatar,
				d.photo,
				d.picture,
				previousUser?.legacyAvatarUrl
			);
			const avatarUrl = pickAvatar(workspaceAvatarUrl, legacyAvatarUrl, previousUser?.avatarUrl);
			
			// Merge no destructivo: nunca pisar datos válidos con campos vacíos
			const firstName = pickText(d.first_name, d.firstName, previousUser?.firstName);
			const lastName = pickText(d.last_name, d.lastName, previousUser?.lastName);
			const username = pickText(
				d.preferred_username,
				d.username,
				previousUser?.username
			) ?? '';
			const nameFromParts = pickText(
				firstName && lastName ? `${firstName} ${lastName}` : null,
				firstName,
				lastName
			);
			const name = pickText(
				d.name,
				nameFromParts,
				previousUser?.name,
				username
			) ?? username;
			const displayName = pickText(
				d.displayName,
				d.display_name,
				name,
				username
			) ?? username;
			const email = pickText(d.email, previousUser?.email) ?? undefined;
			const userId = pickText(d.sub, d.id, previousUser?.id) ?? '';
			
			console.debug('[avatar][auth.loadUserInfo] /v1/auth/me avatar_url recibido:', workspaceAvatarUrl ?? '(null)');
			console.debug('[avatar][auth.loadUserInfo] avatar final para authStore:', avatarUrl ?? '(null)');
			console.debug('[profile][auth.loadUserInfo] first_name:', firstName ?? '(null)', 'last_name:', lastName ?? '(null)', 'avatar_url:', workspaceAvatarUrl ?? '(null)');
			
			authStore.setUser({
				id: userId,
				username,
				email,
				name,
				firstName,
				lastName,
				displayName,
				avatarUrl,
				workspaceAvatarUrl,
				legacyAvatarUrl,
				tenantId: d.tenant_id ?? previousUser?.tenantId,
				roles: d.roles ?? previousUser?.roles ?? [],
				permissions: d.permissions ?? previousUser?.permissions ?? [],
				profileSyncedAt: Date.now()
			});
		}
	} catch (err) {
		console.error('[auth] Error cargando info del usuario:', err);
	}
};

/**
 * Sincroniza el perfil del usuario desde /v1/auth/me
 * - Valida cache TTL (5 minutos) antes de hacer fetch
 * - Extrae first_name, last_name, avatar_url
 * - Retorna objeto con campos de perfil para UI o update de store
 * 
 * Uso:
 * ```
 * const profile = await syncUserProfile(accessToken);
 * if (profile) {
 *   // profile.firstName, profile.lastName, profile.displayName, profile.avatarUrl
 * }
 * ```
 */
export const syncUserProfile = async (accessToken?: string): Promise<{
	firstName: string | null;
	lastName: string | null;
	displayName: string | null;
	avatarUrl: string | null;
	workspaceAvatarUrl: string | null;
	legacyAvatarUrl: string | null;
	rawProfileSyncedAt: number;
} | null> => {
	if (!browser) return null;

	try {
		// Validar cache TTL: 5 minutos (300000ms)
		const PROFILE_CACHE_TTL = 5 * 60 * 1000;
		const { get } = await import('svelte/store');
		const currentState = get(authStore);
		const now = Date.now();
		
		if (
			currentState.user?.profileSyncedAt &&
			(now - currentState.user.profileSyncedAt) < PROFILE_CACHE_TTL
		) {
			console.debug('[profile][sync] Cache válido, no re-fetching /v1/auth/me');
			return {
				firstName: currentState.user.firstName ?? null,
				lastName: currentState.user.lastName ?? null,
				displayName: currentState.user.displayName ?? null,
				avatarUrl: currentState.user.avatarUrl ?? null,
				workspaceAvatarUrl: currentState.user.workspaceAvatarUrl ?? null,
				legacyAvatarUrl: currentState.user.legacyAvatarUrl ?? null,
				rawProfileSyncedAt: currentState.user.profileSyncedAt
			};
		}

		const token = accessToken || getToken();
		if (!token) {
			console.warn('[profile][sync] No token disponible para sincronizar');
			return null;
		}

		// Llamar a loadUserInfo que hace fetch a /v1/auth/me
		await loadUserInfo(token);
		
		// Retornar los nuevos valores desde el store
		const updatedState = get(authStore);
		if (updatedState.user) {
			console.debug('[profile][sync] Perfil sincronizado exitosamente');
			return {
				firstName: updatedState.user.firstName ?? null,
				lastName: updatedState.user.lastName ?? null,
				displayName: updatedState.user.displayName ?? null,
				avatarUrl: updatedState.user.avatarUrl ?? null,
				workspaceAvatarUrl: updatedState.user.workspaceAvatarUrl ?? null,
				legacyAvatarUrl: updatedState.user.legacyAvatarUrl ?? null,
				rawProfileSyncedAt: updatedState.user.profileSyncedAt ?? 0
			};
		}

		return null;
	} catch (err) {
		console.error('[profile][sync] Error sincronizando perfil:', err);
		return null;
	}
};

// ─────────────────────────────────────────────────────────
// Logout
// ─────────────────────────────────────────────────────────

export const logout = async () => {
	if (!browser) return;

	try {
		// Detener el SessionManager
		try {
			const { destroySessionManager } = await import('./session-manager');
			destroySessionManager();
		} catch { }

		// Limpiar store de compañías
		try {
			const { companyStore } = await import('./stores/company.svelte');
			companyStore.clear();
		} catch { }

		// Limpiar estado en memoria
		authStore.reset();
		persistUserInSession(null);

		clearAccessTokenOnDocument();
		// La cookie HttpOnly del refresh_token la limpia el servidor

		// Logout unificado (SSO y password): POST al logout route del servidor.
		// Evita redirección visible al endpoint de Keycloak.
		if (keycloakInstance) {
			try {
			keycloakInstance.clearToken();
			} catch {}
		}

		const form = document.createElement('form');
		form.method = 'POST';
		form.action = '/logout';
		document.body.appendChild(form);
		form.submit();
	} catch (err) {
		console.error('[auth] Error durante logout:', err);
		window.location.href = '/login';
	}
};

// ─────────────────────────────────────────────────────────
// Token accessors
// ─────────────────────────────────────────────────────────

export const hasRole = (role: string): boolean => {
	if (!keycloakInstance?.authenticated) return false;
	return keycloakInstance.hasRealmRole(role);
};

/** Obtiene el access token desde memoria (Keycloak JS o authStore) */
export const getToken = (): string | null => {
	// Prioridad 1: Keycloak JS en memoria
	if (keycloakInstance?.token) return keycloakInstance.token;

	// Prioridad 2: authStore en memoria
	let token: string | null = null;
	const unsub = authStore.subscribe((s) => { token = s.token; });
	unsub();
	if (token) return token;

	// Prioridad 3: cookie no-HttpOnly (fallback para acceso inicial antes del onMount)
	if (browser) return getAccessTokenFromDocument();

	return null;
};

/**
 * Refresca el access token usando el endpoint server-side seguro.
 * El servidor lee el refresh_token de la cookie HttpOnly.
 * @returns true si el refresh fue exitoso
 */
export const refreshAccessToken = async (): Promise<boolean> => {
	if (!browser) return false;

	// Con Keycloak JS activo, usar su mecanismo nativo
	if (keycloakInstance?.authenticated) {
		try {
			const refreshed = await keycloakInstance.updateToken(70);
			if (refreshed || keycloakInstance.token) {
				authStore.setToken(keycloakInstance.token ?? null);
				return true;
			}
		} catch {
			await logout();
			return false;
		}
	}

	// Flujo de contraseña: usar el endpoint server-side seguro
	try {
		const resp = await fetch('/api-sveltekit/auth/silent-refresh', {
			method: 'POST',
			credentials: 'include',
			headers: { 'Content-Type': 'application/json' }
		});

		if (!resp.ok) {
			await logout();
			return false;
		}

		const data = await resp.json() as { access_token?: string };
		if (data.access_token) {
			authStore.setToken(data.access_token);
			setAccessTokenOnDocument(data.access_token);
			return true;
		}
	} catch (err) {
		console.error('[auth] Error en refreshAccessToken:', err);
	}

	await logout();
	return false;
};

/** @deprecated El refresh_token ya no se expone en JS. */
export const getRefreshToken = (): string | null => {
	console.warn('[auth] getRefreshToken() está deprecado — el refresh_token no se expone en JS.');
	return null;
};

export const getKeycloakInstance = (): Keycloak | null => keycloakInstance;
