import type { TipoDeTrilha } from "@/lib/live/evento";
import { ehDaEquipe, lerOferta, lerOfertas, noEnderecoDaLoja } from "@/lib/live/regras";
import { lerCorpo, rotaDeQuemEsta } from "@/lib/live/servidor/rota";
import {
  CAMPOS_DO_PARTICIPANTE,
  cortarTrilhas as cortarNoSfu,
  exigirEquipe,
  ErroDaLive,
  mudarEstado,
  produtoComoOferta,
  publicarEstado,
  slugDaLoja,
  uuidValido,
  type Admin,
  type Participante,
  type SalaDoBanco,
} from "@/lib/live/servidor/sala";
import { esquecerLivesDaLoja } from "@/lib/live/servidor/vitrine";
import { avisarSala } from "@/lib/live/sinal";
import { configuracaoDoSfu, fecharTrilhas } from "@/lib/live/sfu";

export const runtime = "nodejs";

async function lerPessoa(admin: Admin, sala: SalaDoBanco, id: unknown): Promise<Participante> {
  if (!uuidValido(id)) throw new ErroDaLive("Pessoa inválida.", 400);
  const { data } = await admin
    .from("live_participantes")
    .select(CAMPOS_DO_PARTICIPANTE)
    .eq("id", id)
    .eq("sala_id", sala.id)
    .maybeSingle();
  if (!data) throw new ErroDaLive("Esta pessoa não está mais na sala.", 404);
  return data as Participante;
}

/* Fecha no SFU as trilhas de alguém (o microfone de quem perdeu a fala, por exemplo). */
async function cortarTrilhas(_admin: Admin, pessoa: Participante, tipos: TipoDeTrilha[]) {
  const cfg = configuracaoDoSfu();
  return cortarNoSfu(pessoa, tipos, (sessao, mids) =>
    cfg ? fecharTrilhas(cfg, sessao, { tracks: mids.map((mid) => ({ mid })), force: true }) : Promise.resolve(null),
  );
}

function minutosValidos(valor: unknown): number | null {
  const n = Number(valor);
  return Number.isFinite(n) && n >= 1 && n <= 600 ? Math.round(n) : null;
}

/*
 * O que só quem apresenta faz: começar e encerrar, liberar e tirar a fala,
 * apagar mensagem, tirar alguém da sala, fixar recado, soltar oferta e (no
 * liveshop) mostrar um produto.
 */
export async function POST(req: Request, { params }: { params: Promise<{ salaId: string }> }) {
  const { salaId } = await params;
  return rotaDeQuemEsta(req, salaId, async ({ admin, sala, eu }) => {
    exigirEquipe(eu);
    const corpo = await lerCorpo(req);
    const acao = String(corpo.acao || "");
    const canal = { id: sala.id, canal: sala.canal };
    /* O liveshop da loja muda com estas: a lista guardada sai. */
    if (sala.vitrine && ["iniciar", "encerrar", "foco"].includes(acao)) esquecerLivesDaLoja(await slugDaLoja(admin, sala.user_id));

    switch (acao) {
      case "iniciar": {
        if (sala.status === "ao_vivo") return { ok: true, estado: await publicarEstado(admin, sala.id) };
        if (sala.status !== "agendada") throw new ErroDaLive("Esta live já terminou.", 410);
        const { data, error } = await admin.rpc("live_reservar_uso", {
          p_empresa: sala.user_id,
          p_sala: sala.id,
          p_origem: sala.vaga === "pacote" ? "compra" : "gratis",
          p_compra: sala.vaga === "pacote" ? sala.vaga_compra_id : null,
        });
        if (error) throw new ErroDaLive("Não foi possível começar agora. Tente de novo.", 500);
        const uso = data as { resultado: string; pessoas?: number };
        if (uso.resultado === "sem_saldo") {
          throw new ErroDaLive(
            sala.vaga === "pacote"
              ? "O pacote escolhido para esta transmissão acabou ou venceu. No painel, troque a vaga dela ou compre mais em Assinatura → Produtos Digitais."
              : "As transmissões grátis deste mês acabaram. Compre mais em Assinatura → Produtos Digitais no painel e escolha o pacote nesta transmissão.",
            402,
            { motivo: "sem_saldo" },
          );
        }
        await admin
          .from("live_salas")
          .update({ status: "ao_vivo", iniciada_em: new Date().toISOString(), limite_pessoas: uso.pessoas ?? null, atualizado_em: new Date().toISOString() })
          .eq("id", sala.id)
          .eq("status", "agendada");
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "encerrar": {
        if (sala.status === "encerrada") return { ok: true };
        await admin
          .from("live_salas")
          .update({ status: "encerrada", encerrada_em: new Date().toISOString(), atualizado_em: new Date().toISOString() })
          .eq("id", sala.id);
        await admin.from("live_participantes").update({ mao_em: null, pode_falar: false }).eq("sala_id", sala.id).not("mao_em", "is", null);
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "fala": {
        const pessoa = await lerPessoa(admin, sala, corpo.participante);
        if (ehDaEquipe(pessoa.papel)) return { ok: true };
        const pode = corpo.pode === true;
        if (pode && sala.participacao !== "mao") throw new ErroDaLive("Nesta live só quem apresenta fala.", 403);
        const trilhas = pode ? pessoa.trilhas : await cortarTrilhas(admin, pessoa, sala.cameras_alunos ? ["mic"] : ["mic", "cam"]);
        await admin.from("live_participantes").update({ pode_falar: pode, mao_em: null, trilhas }).eq("id", pessoa.id);
        await avisarSala(admin, canal, [{ t: "fala", participante: pessoa.id, pode }]);
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "baixar_mao": {
        const pessoa = await lerPessoa(admin, sala, corpo.participante);
        await admin.from("live_participantes").update({ mao_em: null }).eq("id", pessoa.id);
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "baixar_todas": {
        await admin.from("live_participantes").update({ mao_em: null }).eq("sala_id", sala.id).not("mao_em", "is", null);
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "apagar": {
        if (!uuidValido(corpo.mensagem)) throw new ErroDaLive("Mensagem inválida.", 400);
        await admin.from("live_mensagens").update({ apagada_em: new Date().toISOString() }).eq("id", corpo.mensagem).eq("sala_id", sala.id);
        await avisarSala(admin, canal, [{ t: "msg-fora", id: String(corpo.mensagem) }]);
        return { ok: true };
      }

      case "tirar": {
        const pessoa = await lerPessoa(admin, sala, corpo.participante);
        if (ehDaEquipe(pessoa.papel)) throw new ErroDaLive("Quem apresenta não pode ser tirado.", 400);
        const trilhas = await cortarTrilhas(admin, pessoa, ["cam", "mic", "tela", "tela-audio"]);
        await admin
          .from("live_participantes")
          .update({ bloqueado: true, saiu_em: new Date().toISOString(), mao_em: null, pode_falar: false, trilhas })
          .eq("id", pessoa.id);
        await avisarSala(admin, canal, [{ t: "fora", participante: pessoa.id }]);
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "fixar": {
        const texto = typeof corpo.texto === "string" ? corpo.texto.trim().slice(0, 240) : "";
        const link = typeof corpo.link === "string" && /^https:\/\//.test(corpo.link.trim()) ? corpo.link.trim().slice(0, 1000) : null;
        const rotuloDoLink = typeof corpo.rotuloDoLink === "string" ? corpo.rotuloDoLink.trim().slice(0, 40) : null;
        return { ok: true, estado: await mudarEstado(admin, sala, { fixado: texto ? { texto, link, rotuloDoLink } : null }) };
      }

      case "soltar": {
        /* Um item pronto (pelo id) ou um feito na hora. */
        const prontos = lerOfertas(sala.itens);
        const pronto = typeof corpo.item === "string" ? prontos.find((i) => i.id === corpo.item) : null;
        const oferta = pronto ?? lerOferta(corpo.oferta);
        if (!oferta) throw new ErroDaLive("Este item não existe mais.", 404);
        const minutos = minutosValidos(corpo.minutos);
        const slug = await slugDaLoja(admin, sala.user_id);
        const comPrazo = { ...noEnderecoDaLoja(oferta, slug), ate: minutos ? new Date(Date.now() + minutos * 60000).toISOString() : null };
        /* Link solto também fica fixado no bate-papo, como na prévia. */
        const mudancas: Record<string, unknown> = { oferta: comPrazo };
        if (oferta.tipo === "link" && oferta.destino && /^https:\/\//.test(oferta.destino)) {
          mudancas.fixado = { texto: oferta.detalhe ? `${oferta.titulo}: ${oferta.detalhe}` : oferta.titulo, link: oferta.destino, rotuloDoLink: "Abrir" };
        }
        return { ok: true, estado: await mudarEstado(admin, sala, mudancas) };
      }

      case "tirar_oferta":
        return { ok: true, estado: await mudarEstado(admin, sala, { oferta: null }) };

      case "foco": {
        if (corpo.produto === null) return { ok: true, estado: await mudarEstado(admin, sala, { foco: null }) };
        const slug = await slugDaLoja(admin, sala.user_id);
        const oferta = await produtoComoOferta(admin, sala, String(corpo.produto || ""), slug);
        if (!oferta) throw new ErroDaLive("Este produto não está à venda na loja.", 404);
        return { ok: true, estado: await mudarEstado(admin, sala, { foco: oferta }) };
      }

      case "silenciar": {
        /* Como no Meet: quem organiza fecha o microfone de alguém (a pessoa pode abrir de novo se a sala deixa). */
        const pessoa = await lerPessoa(admin, sala, corpo.participante);
        if (ehDaEquipe(pessoa.papel) && pessoa.id !== eu.id) throw new ErroDaLive("Não dá para fechar o microfone de quem apresenta.", 400);
        const trilhas = await cortarTrilhas(admin, pessoa, ["mic"]);
        await admin.from("live_participantes").update({ trilhas }).eq("id", pessoa.id);
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "parar_tela": {
        /* Quem organiza tira a tela de quem está apresentando. */
        const pessoa = await lerPessoa(admin, sala, corpo.participante);
        const trilhas = await cortarTrilhas(admin, pessoa, ["tela", "tela-audio"]);
        await admin.from("live_participantes").update({ trilhas }).eq("id", pessoa.id);
        return { ok: true, estado: await publicarEstado(admin, sala.id) };
      }

      case "modo_gravacao": {
        const modo = corpo.modo === "sala" ? "sala" : "palco";
        await admin.from("live_salas").update({ gravacao_modo: modo, atualizado_em: new Date().toISOString() }).eq("id", sala.id);
        return { ok: true, modo };
      }

      default:
        throw new ErroDaLive("Ação desconhecida.", 400);
    }
  });
}
