import { createExternalSupabaseClient } from "@/lib/api-v1/auth";
import { createAdminClient } from "@/lib/supabase-server";

import { criarGuardaDeSessoes, type SessaoGuardada } from "./guarda-de-sessoes";

/**
 * A sessão da pessoa por trás da chave.
 *
 * O assistente não age com a chave de serviço, que ignora as travas do banco.
 * Ele age com uma sessão de verdade da pessoa que criou a chave, igual à que o
 * painel usa. Assim quem decide o que ela enxerga continua sendo o banco: as
 * políticas de cada tabela, as permissões do funcionário e o isolamento entre
 * empresas valem aqui exatamente como valem na tela.
 *
 * A sessão nasce no servidor, sem senha e sem e-mail: gera-se um link de
 * entrada para a conta e ele é trocado por uma sessão ali mesmo. É o mesmo
 * caminho que o roteiro de capturas da landing já usa para entrar na Aurora.
 * Os tokens nunca saem deste processo.
 *
 * O link de entrada é gerado UMA vez por pessoa enquanto o servidor estiver de
 * pé. Depois disso a mesma sessão é renovada de hora em hora, como o navegador
 * faz. Gerar um link novo a cada hora teria dois custos: cada um deixaria uma
 * sessão a mais guardada no banco para sempre, e cada um anularia um link de
 * "esqueci minha senha" que a pessoa tivesse acabado de pedir.
 *
 * Revogar uma chave não mexe na sessão guardada. Ela só é usada quando chega
 * uma chave válida da mesma pessoa, e jogá-la fora a cada revogação deixaria
 * qualquer um gastar, criando e revogando chaves, a cota de sessões novas que
 * o banco dá ao servidor inteiro.
 *
 * A regra de quando reaproveitar, renovar ou criar mora em
 * `guarda-de-sessoes.ts`. Aqui fica só a conversa com o banco.
 */

declare global {
  var __sessoesDosAssistentes: Map<string, SessaoGuardada> | undefined;
  var __sessoesDosAssistentesEmCurso: Map<string, Promise<SessaoGuardada>> | undefined;
}

type SessaoDoBanco = { access_token?: string | null; refresh_token?: string | null; expires_at?: number | null } | null | undefined;

function paraGuardar(sessao: SessaoDoBanco): SessaoGuardada | null {
  if (!sessao?.access_token) return null;
  return {
    accessToken: sessao.access_token,
    refreshToken: sessao.refresh_token || null,
    venceEm: sessao.expires_at ? sessao.expires_at * 1000 : Date.now() + 55 * 60 * 1000,
  };
}

async function criar(usuario: { id: string }): Promise<SessaoGuardada> {
  const admin = createAdminClient();

  /* O e-mail é lido na hora, pelo id. O link de entrada é pedido por e-mail, e
     um e-mail desatualizado (a pessoa trocou o dela há pouco) abriria a sessão
     de outra conta, ou criaria uma conta nova com o e-mail antigo. */
  const { data: conta, error: erroDaConta } = await admin.auth.admin.getUserById(usuario.id);
  const email = conta?.user?.email;
  if (erroDaConta || !email) {
    throw new Error(erroDaConta?.message || "A conta desta chave não existe mais.");
  }

  const { data: link, error: erroDoLink } = await admin.auth.admin.generateLink({ type: "magiclink", email });
  const hash = link?.properties?.hashed_token;
  if (erroDoLink || !hash) {
    throw new Error(erroDoLink?.message || "Não foi possível preparar a sessão da conta.");
  }
  if (link.user?.id !== usuario.id) {
    throw new Error("O link de entrada não saiu para a conta desta chave.");
  }

  const { data, error } = await createExternalSupabaseClient().auth.verifyOtp({ token_hash: hash, type: "magiclink" });
  const sessao = paraGuardar(data?.session);
  if (error || !sessao) {
    throw new Error(error?.message || "Não foi possível abrir a sessão da conta.");
  }

  /* A sessão tem de ser da pessoa da chave, e de mais ninguém. Se não for, ela
     é encerrada e nada é guardado. */
  if (data.user?.id !== usuario.id) {
    void admin.auth.admin.signOut(sessao.accessToken, "local").then(
      () => undefined,
      () => undefined
    );
    throw new Error("A sessão aberta não é da conta desta chave.");
  }
  return sessao;
}

/* `null` = a sessão não vale mais (a pessoa saiu de todos os aparelhos, trocou a senha). */
async function renovar(refreshToken: string): Promise<SessaoGuardada | null> {
  try {
    const { data, error } = await createExternalSupabaseClient().auth.refreshSession({ refresh_token: refreshToken });
    return error ? null : paraGuardar(data?.session);
  } catch {
    return null;
  }
}

function guarda() {
  /* Os mapas moram no `globalThis` para sobreviver à recarga do código em
     desenvolvimento; a guarda em si é só regra e pode nascer a cada pedido. */
  globalThis.__sessoesDosAssistentes ??= new Map();
  globalThis.__sessoesDosAssistentesEmCurso ??= new Map();
  return criarGuardaDeSessoes({
    criar,
    renovar,
    guardadas: globalThis.__sessoesDosAssistentes,
    emCurso: globalThis.__sessoesDosAssistentesEmCurso,
  });
}

/** O token de acesso da pessoa, reaproveitado, renovado ou novo. */
export function sessaoDoUsuario(usuarioId: string): Promise<string> {
  return guarda().obter({ id: usuarioId });
}
