export type SupabaseAuthUser = {
  id: string;
  email: string;
  user_metadata?: {
    avatar_url?: string;
    full_name?: string;
    name?: string;
  };
};

export type SupabaseAuthSession = {
  accessToken: string;
  refreshToken: string;
  expiresAt: number;
  user: SupabaseAuthUser;
};

type TokenResponse = {
  access_token: string;
  refresh_token: string;
  expires_at?: number;
  expires_in?: number;
  user?: SupabaseAuthUser;
};

const supabaseUrl = String(import.meta.env.VITE_SUPABASE_URL ?? "")
  .trim()
  .replace(/\/+$/, "");
const publishableKey = String(import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY ?? "").trim();
const anonKey = String(import.meta.env.VITE_SUPABASE_ANON_KEY ?? "").trim();
const supabaseKey = publishableKey || anonKey;

const SESSION_STORAGE_KEY = "glm-tax-auth-session-v1";
const OAUTH_NAVIGATION_KEY = "glm-tax-oauth-navigation-v3";
const OAUTH_FRESH_QUERY_KEY = "__glm_auth_fresh";
const EXPIRY_MARGIN_SECONDS = 60;

export const isSupabaseConfigured = Boolean(supabaseUrl && supabaseKey);

export function getSupabaseConfigurationMessage(): string | null {
  if (isSupabaseConfigured) return null;

  const missing: string[] = [];
  if (!supabaseUrl) missing.push("VITE_SUPABASE_URL");
  if (!supabaseKey) {
    missing.push("VITE_SUPABASE_PUBLISHABLE_KEY o VITE_SUPABASE_ANON_KEY");
  }

  return `Falta configurar ${missing.join(" y ")} en el archivo .env.`;
}

export function getOAuthRedirectUrl(): string {
  // Se conserva exactamente la misma URL que ya está permitida en Supabase.
  // La actualización del documento se hace después del retorno, desde el
  // navegador, por lo que no requiere agregar una URL nueva en EasyPanel.
  return new URL(import.meta.env.BASE_URL, window.location.origin).toString();
}

export async function signInWithGoogle(): Promise<void> {
  assertConfigured();

  const redirectUrl = getOAuthRedirectUrl();
  markOAuthNavigation();

  // Actualiza la entrada HTML en la caché HTTP antes de salir hacia Google.
  // Así, el retorno de OAuth no reutiliza un index.html anterior del servidor.
  await warmLatestApplicationEntry(redirectUrl);

  const params = new URLSearchParams({
    provider: "google",
    redirect_to: redirectUrl,
    prompt: "select_account",
  });

  // replace evita que el navegador restaure la pantalla previa mediante BFCache.
  window.location.replace(`${supabaseUrl}/auth/v1/authorize?${params.toString()}`);
}

export async function restoreSession(): Promise<SupabaseAuthSession | null> {
  assertConfigured();

  const callbackSession = await consumeOAuthCallback();
  if (callbackSession) return callbackSession;

  const stored = readStoredSession();
  if (!stored) return null;

  try {
    return await ensureFreshSession(stored);
  } catch {
    clearStoredSession();
    return null;
  }
}

export async function verifyTaxCalendarAccess(
  session: SupabaseAuthSession,
): Promise<{ allowed: boolean; session: SupabaseAuthSession }> {
  const freshSession = await ensureFreshSession(session);
  const response = await fetch(`${supabaseUrl}/rest/v1/rpc/has_tax_calendar_access`, {
    method: "POST",
    headers: authenticatedHeaders(freshSession.accessToken),
    body: "{}",
  });

  if (!response.ok) {
    throw new Error(await getResponseError(response, "No se pudo verificar el acceso."));
  }

  const allowed = (await response.json()) === true;
  return { allowed, session: freshSession };
}

export async function signOut(session?: SupabaseAuthSession | null): Promise<void> {
  try {
    if (session?.accessToken && isSupabaseConfigured) {
      await fetch(`${supabaseUrl}/auth/v1/logout`, {
        method: "POST",
        headers: authenticatedHeaders(session.accessToken),
      });
    }
  } catch {
    // El cierre local debe completarse incluso si Supabase no está disponible.
  } finally {
    clearStoredSession();
    clearOAuthNavigation();
  }
}

async function consumeOAuthCallback(): Promise<SupabaseAuthSession | null> {
  const hash = new URLSearchParams(window.location.hash.replace(/^#/, ""));
  const oauthError = hash.get("error_description") ?? hash.get("error");

  if (oauthError) {
    clearOAuthNavigation();
    cleanOAuthFragment();
    throw new Error(oauthError.replace(/\+/g, " "));
  }

  const accessToken = hash.get("access_token");
  const refreshToken = hash.get("refresh_token");
  if (!accessToken || !refreshToken) return null;

  const expiresAt = resolveExpiry(hash.get("expires_at"), hash.get("expires_in"));

  // Retira los tokens y el parámetro temporal de la barra de direcciones.
  clearOAuthNavigation();
  cleanOAuthFragment();

  const user = await fetchUser(accessToken);
  const session = { accessToken, refreshToken, expiresAt, user };

  storeSession(session);
  return session;
}

async function ensureFreshSession(session: SupabaseAuthSession): Promise<SupabaseAuthSession> {
  const nowSeconds = Math.floor(Date.now() / 1000);
  if (session.expiresAt > nowSeconds + EXPIRY_MARGIN_SECONDS) return session;

  const response = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
    method: "POST",
    headers: publicHeaders(),
    body: JSON.stringify({ refresh_token: session.refreshToken }),
  });

  if (!response.ok) {
    throw new Error(await getResponseError(response, "La sesión expiró."));
  }

  const tokenData = (await response.json()) as TokenResponse;
  const refreshed: SupabaseAuthSession = {
    accessToken: tokenData.access_token,
    refreshToken: tokenData.refresh_token,
    expiresAt: resolveExpiry(tokenData.expires_at, tokenData.expires_in),
    user: tokenData.user ?? (await fetchUser(tokenData.access_token)),
  };

  storeSession(refreshed);
  return refreshed;
}

async function fetchUser(accessToken: string): Promise<SupabaseAuthUser> {
  const response = await fetch(`${supabaseUrl}/auth/v1/user`, {
    headers: authenticatedHeaders(accessToken),
  });

  if (!response.ok) {
    throw new Error(await getResponseError(response, "No se pudo cargar el usuario."));
  }

  const user = (await response.json()) as Partial<SupabaseAuthUser>;
  if (!user.id || !user.email) {
    throw new Error("Supabase no devolvió un usuario válido.");
  }

  return user as SupabaseAuthUser;
}

async function warmLatestApplicationEntry(url: string): Promise<void> {
  try {
    await fetch(url, {
      method: "GET",
      cache: "reload",
      credentials: "same-origin",
      headers: {
        "Cache-Control": "no-cache",
        Pragma: "no-cache",
      },
    });
  } catch {
    // El login debe continuar aunque la actualización preventiva no responda.
  }
}

function markOAuthNavigation(): void {
  try {
    sessionStorage.setItem(
      OAUTH_NAVIGATION_KEY,
      JSON.stringify({ startedAt: Date.now(), redirectUrl: getOAuthRedirectUrl() }),
    );
  } catch {
    // Algunos navegadores pueden bloquear sessionStorage; el OAuth sigue funcionando.
  }
}

function clearOAuthNavigation(): void {
  try {
    sessionStorage.removeItem(OAUTH_NAVIGATION_KEY);
  } catch {
    // No impide cerrar o restaurar la sesión.
  }
}

function publicHeaders(): HeadersInit {
  return {
    apikey: supabaseKey,
    "Content-Type": "application/json",
  };
}

function authenticatedHeaders(accessToken: string): HeadersInit {
  return {
    ...publicHeaders(),
    Authorization: `Bearer ${accessToken}`,
  };
}

function readStoredSession(): SupabaseAuthSession | null {
  try {
    const raw = localStorage.getItem(SESSION_STORAGE_KEY);
    if (!raw) return null;

    const parsed = JSON.parse(raw) as Partial<SupabaseAuthSession>;
    if (
      !parsed.accessToken ||
      !parsed.refreshToken ||
      !parsed.expiresAt ||
      !parsed.user?.id ||
      !parsed.user.email
    ) {
      clearStoredSession();
      return null;
    }

    return parsed as SupabaseAuthSession;
  } catch {
    clearStoredSession();
    return null;
  }
}

function storeSession(session: SupabaseAuthSession): void {
  localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));
}

function clearStoredSession(): void {
  localStorage.removeItem(SESSION_STORAGE_KEY);
}

function cleanOAuthFragment(): void {
  const cleanUrl = new URL(window.location.href);
  cleanUrl.hash = "";
  cleanUrl.searchParams.delete(OAUTH_FRESH_QUERY_KEY);
  cleanUrl.searchParams.delete("oauth_return");

  const search = cleanUrl.searchParams.toString();
  window.history.replaceState(
    null,
    document.title,
    `${cleanUrl.pathname}${search ? `?${search}` : ""}`,
  );
}

function resolveExpiry(
  expiresAt: string | number | null | undefined,
  expiresIn: string | number | null | undefined,
): number {
  const absolute = Number(expiresAt);
  if (Number.isFinite(absolute) && absolute > 0) return absolute;

  const duration = Number(expiresIn);
  return Math.floor(Date.now() / 1000) + (Number.isFinite(duration) ? duration : 3600);
}

async function getResponseError(response: Response, fallback: string): Promise<string> {
  try {
    const payload = (await response.json()) as {
      message?: string;
      msg?: string;
      error?: string;
      error_description?: string;
    };
    return payload.message ?? payload.msg ?? payload.error_description ?? payload.error ?? fallback;
  } catch {
    return fallback;
  }
}

function assertConfigured(): void {
  const message = getSupabaseConfigurationMessage();
  if (message) throw new Error(message);
}
