import { quemAssiste } from "@/lib/digitais/servidor/curso";
import { origemDaRequisicao, tentativaPermitida } from "@/lib/limite-de-tentativas";
import type { PapelNaSala } from "@/lib/live/evento";
import { ehDaEquipe, lerOfertas, momentoDaSala, noEnderecoDaLoja, nomeNaSala } from "@/lib/live/regras";
import { lerCorpo, respostaDaLive, rotaDaSala } from "@/lib/live/servidor/rota";
import {
  CAMPOS_DO_PARTICIPANTE,
  capacidadeDaSala,
  cortarTrilhas,
  chaveDoApresentadorValida,
  conviteValido,
  ErroDaLive,
  mensagensRecentes,
  montarEstado,
  novoToken,
  participanteDaCredencial,
  pessoasOnline,
  produtosDaVitrine,
  publicarEstado,
  resumo,
  slugDaLoja,
  contarPublico,
  type Participante,
} from "@/lib/live/servidor/sala";
import { lerIdentidade } from "@/lib/live/servidor/identidade";
import { avisarSala, chavesDaLive, topicoDaSala } from "@/lib/live/sinal";
import { configuracaoDoSfu, fecharTrilhas, servidoresIce } from "@/lib/live/sfu";

export const runtime = "nodejs";

/*
 * Entrar na sala. Quem é cada um:
 * - quem apresenta: o link assinado que o painel gera ("Entrar ao vivo");
 * - convidado para apresentar junto: o link de convite da live;
 * - quem comprou ou garantiu a vaga: a sessão da área de membros ou a chave
 *   da compra/do e-mail neste navegador;
 * - quem está logado no Ktalogu (o painel assina quem é): da empresa dona da
 *   sala, entra como quem organiza; de outra empresa, entra com o nome e a
 *   logo dela (o nome dá para trocar antes de entrar);
 * - live aberta: qualquer pessoa, só com o nome.
 *
 * Quem volta (recarregou a página) manda a credencial que já tinha: ela é
 * solta (não conta duas vezes para o limite) e a pessoa entra de novo. Quem entra de novo pelo mesmo cadastro em
 * outro aparelho tira o aparelho anterior (uma vaga, uma tela).
 */
export async function POST(req: Request, { params }: { params: Promise<{ salaId: string }> }) {
  const { salaId } = await params;
  if (!tentativaPermitida(`live-entrar:${origemDaRequisicao(req)}`, 30, 60)) {
    return respostaDaLive({ error: "Muitas tentativas. Espere um minuto." }, 429);
  }
  return rotaDaSala(salaId, async (admin, sala) => {
    const corpo = await lerCorpo(req);
    const momento = momentoDaSala(sala);
    const slug = await slugDaLoja(admin, sala.user_id);

    let eu: Participante | null = null;
    let token: string | null = null;
    const anterior = await participanteDaCredencial(admin, sala.id, corpo.anterior);
    const identidade = lerIdentidade(corpo.identidade, sala.id);
    /*
     * Voltando (recarregou a página, a internet caiu): a vaga de antes é solta
     * aqui e a pessoa ganha uma nova. Assim o aviso de saída da aba antiga,
     * que pode chegar depois, nunca derruba a entrada nova.
     */
    if (anterior?.bloqueado) throw new ErroDaLive("A loja tirou você desta live.", 403, { motivo: "bloqueado" });
    if (anterior && !anterior.saiu_em) {
      const cfg = configuracaoDoSfu();
      if (cfg) {
        await cortarTrilhas(anterior, ["cam", "mic", "tela", "tela-audio"], (sessao, mids) =>
          fecharTrilhas(cfg, sessao, { tracks: mids.map((mid) => ({ mid })), force: true }),
        ).catch(() => null);
      }
      await admin.from("live_participantes").update({ saiu_em: new Date().toISOString(), mao_em: null, trilhas: {} }).eq("id", anterior.id);
    }

    {
      let papel: PapelNaSala | null = null;
      let nome: string | null = null;
      let email: string | null = null;
      let foto: string | null = null;
      let clienteId: string | null = null;
      let empresaId: string | null = null;

      if (chaveDoApresentadorValida(sala.id, corpo.apresentar) || identidade?.dono) {
        papel = "apresentador";
        const { data: loja } = await admin.from("profiles").select("store_name,catalog_name,catalog_logo_url,logo_url").eq("id", sala.user_id).maybeSingle();
        const perfil = loja as { store_name?: string | null; catalog_name?: string | null; catalog_logo_url?: string | null; logo_url?: string | null } | null;
        const nomeDaLoja = perfil?.catalog_name || perfil?.store_name;
        const logoDaLoja = [perfil?.logo_url, perfil?.catalog_logo_url].find((u) => typeof u === "string" && u.startsWith("https://")) ?? null;
        /* Quem organiza pode trocar o nome antes de entrar; sem nome, vale o da live ou o da loja. */
        nome = nomeNaSala(corpo.nome) || sala.apresentador_nome || nomeNaSala(nomeDaLoja) || "Quem apresenta";
        foto = sala.apresentador_foto || logoDaLoja;
        empresaId = sala.user_id;
      } else if (conviteValido(sala, corpo.convite)) {
        papel = "convidado";
        nome = nomeNaSala(corpo.nome);
        if (!nome) throw new ErroDaLive("Diga o seu nome para entrar.", 400, { motivo: "nome" });
      } else {
        const quem = sala.product_id ? await quemAssiste(admin, { slug, lojaId: sala.user_id, produtoId: sala.product_id, tipo: "live" }) : null;
        if (quem?.tipo === "aluno") {
          papel = "aluno";
          clienteId = quem.clienteId;
          const { data } = await admin.from("clients").select("name,email,avatar_url").eq("id", quem.clienteId).maybeSingle();
          const cliente = data as { name?: string | null; email?: string | null; avatar_url?: string | null } | null;
          nome = nomeNaSala(cliente?.name) || "Participante";
          email = cliente?.email ?? null;
          foto = typeof cliente?.avatar_url === "string" && cliente.avatar_url.startsWith("https://") ? cliente.avatar_url : null;
        } else if (quem?.tipo === "equipe") {
          papel = "visitante";
          nome = "Equipe da loja (prévia)";
        } else if (sala.acesso === "aberta" && identidade) {
          /* Assinante do Ktalogu de outra empresa: entra com a empresa dele (o nome dá para trocar). */
          papel = "visitante";
          nome = nomeNaSala(corpo.nome) || identidade.nome;
          foto = identidade.logo;
          empresaId = identidade.empresa;
        } else if (sala.acesso === "aberta") {
          papel = "visitante";
          /* O mini player do liveshop entra sem pedir nome (a pessoa só quer ver). */
          nome = nomeNaSala(corpo.nome) ?? (corpo.vitrine === true ? "Visitante" : null);
          if (!nome) throw new ErroDaLive("Diga o seu nome para entrar.", 400, { motivo: "nome" });
        } else if (quem?.tipo === "fechado") {
          throw new ErroDaLive(quem.motivo, 403, { motivo: "fechado" });
        } else {
          throw new ErroDaLive(
            sala.acesso === "cadastro" ? "Garanta a sua vaga grátis para entrar." : "Garanta a sua vaga para entrar.",
            403,
            { motivo: "comprar", produtoId: sala.product_id },
          );
        }
      }

      if (momento === "cancelada") throw new ErroDaLive("Esta live foi cancelada pela loja.", 410, { motivo: "cancelada" });
      if (!ehDaEquipe(papel) && momento === "cedo") {
        throw new ErroDaLive("A sala ainda não abriu.", 425, { motivo: "cedo", comecaEm: sala.comeca_em, esperaMin: sala.espera_min });
      }
      if (!ehDaEquipe(papel) && momento === "encerrada") throw new ErroDaLive("Esta live já terminou.", 410, { motivo: "encerrada" });

      if (clienteId) {
        const { data: bloqueio } = await admin
          .from("live_participantes")
          .select("id")
          .eq("sala_id", sala.id)
          .eq("client_id", clienteId)
          .eq("bloqueado", true)
          .limit(1);
        if ((bloqueio ?? []).length) throw new ErroDaLive("A loja tirou você desta live.", 403, { motivo: "bloqueado" });
      }

      if (!ehDaEquipe(papel)) {
        const [pessoas, capacidade] = await Promise.all([pessoasOnline(admin, sala.id), capacidadeDaSala(admin, sala)]);
        const ocupadas = contarPublico(pessoas.filter((p) => !clienteId || p.client_id !== clienteId));
        if (ocupadas >= capacidade) {
          throw new ErroDaLive("A sala está cheia agora. Tente de novo em alguns minutos.", 409, { motivo: "cheia" });
        }
      }

      token = novoToken();
      const { data: criado, error } = await admin
        .from("live_participantes")
        .insert({ sala_id: sala.id, papel, nome, email, foto, client_id: clienteId, empresa_id: empresaId, token_hash: resumo(token) })
        .select(CAMPOS_DO_PARTICIPANTE)
        .single();
      if (error || !criado) throw new ErroDaLive("Não foi possível entrar agora. Tente de novo.", 500);
      eu = criado as Participante;

      /* Uma vaga, uma tela: o mesmo cadastro em outro aparelho sai. */
      if (clienteId) {
        const { data: antigos } = await admin
          .from("live_participantes")
          .update({ saiu_em: new Date().toISOString(), mao_em: null })
          .eq("sala_id", sala.id)
          .eq("client_id", clienteId)
          .neq("id", eu.id)
          .is("saiu_em", null)
          .select("id");
        for (const antigo of (antigos ?? []) as Array<{ id: string }>) {
          await avisarSala(admin, { id: sala.id, canal: sala.canal }, [{ t: "fora", participante: antigo.id }]);
        }
      }
    }

    const equipe = ehDaEquipe(eu.papel);
    const [chaves, ice, mensagens, estado, vitrine] = await Promise.all([
      chavesDaLive(admin),
      servidoresIce(),
      mensagensRecentes(admin, sala, eu),
      publicarEstado(admin, sala.id),
      equipe ? produtosDaVitrine(admin, sala, slug) : Promise.resolve([]),
    ]);
    const pessoas = estado ? null : await pessoasOnline(admin, sala.id);

    return {
      eu: { id: eu.id, credencial: `${eu.id}.${token}`, papel: eu.papel, nome: eu.nome, foto: eu.foto, podeFalar: eu.pode_falar },
      sala: {
        id: sala.id,
        titulo: sala.titulo,
        comecaEm: sala.comeca_em,
        duracaoMin: sala.duracao_min,
        participacao: sala.participacao,
        camerasAlunos: sala.cameras_alunos,
        acesso: sala.acesso,
        produtoId: sala.product_id,
        apresentador: sala.apresentador_nome,
        gravar: sala.gravar,
        gravacaoPara: sala.gravacao_para,
        gravacaoModo: sala.gravacao_modo,
        categoria: sala.categoria,
        codigo: sala.codigo,
        slug,
      },
      canal: topicoDaSala(sala.canal),
      chavePublica: chaves.publica,
      horaDoServidor: Date.now(),
      sfu: Boolean(configuracaoDoSfu()),
      ice,
      estado: estado ?? montarEstado(sala, pessoas ?? []),
      mensagens,
      /* O que a loja deixou pronto para soltar: só quem apresenta vê. */
      itens: equipe ? lerOfertas(sala.itens).map((o) => noEnderecoDaLoja(o, slug)) : [],
      /* Liveshop: os produtos que quem apresenta pode passar na tela. */
      vitrine,
    };
  });
}
