import { createHash, randomBytes } from "node:crypto";

import type { SupabaseClient } from "@supabase/supabase-js";

import { baseDaLojaSemPedido } from "@/lib/digitais/fila-regras";
import { lerConfigDigital } from "@/lib/digitais/produto";
import { ErroDigital } from "@/lib/digitais/servidor/contexto";
import { lerLojaDosDigitais } from "@/lib/digitais/servidor/loja";

import { chaveDoApresentador } from "../interno";
import { lerItem, salaParaOBanco, situacaoDaLive, type DadosDaLive, type ItemParaSoltar } from "../sala";

/*
 * As lives da loja no painel: criar (produto + sala + o acesso que a compra
 * libera), alterar, cancelar, e os links de quem apresenta.
 */

type Admin = SupabaseClient;

export const CAMPOS_DA_LIVE =
  "id,user_id,product_id,titulo,comeca_em,duracao_min,espera_min,acesso,vender_durante,limite_vagas,participacao,cameras_alunos,chat,gravar,gravacao_para,gravacao_modo,categoria,codigo,vaga,vaga_compra_id,itens,vitrine,produtos_vitrine,apresentador_nome,apresentador_bio,apresentador_foto,aprender,status,iniciada_em,encerrada_em,limite_pessoas,pico_pessoas,convite_hash,gravacao_status,gravacao_bytes,gravacao_segundos,criado_em";

export type LiveNoBanco = {
  id: string;
  user_id: string;
  product_id: string | null;
  titulo: string;
  comeca_em: string;
  duracao_min: number;
  espera_min: number;
  acesso: "paga" | "cadastro" | "aberta";
  vender_durante: boolean;
  limite_vagas: number | null;
  participacao: "mao" | "so_apresentador" | "livre";
  cameras_alunos: boolean;
  chat: "aberto" | "perguntas" | "desligado";
  gravar: boolean;
  gravacao_para: "compradores" | "ninguem";
  gravacao_modo: "palco" | "sala";
  categoria: "venda" | "reuniao";
  codigo: string;
  vaga: "gratis" | "pacote";
  vaga_compra_id: string | null;
  itens: unknown;
  vitrine: boolean;
  produtos_vitrine: string[];
  apresentador_nome: string | null;
  apresentador_bio: string | null;
  apresentador_foto: string | null;
  aprender: unknown;
  status: "agendada" | "ao_vivo" | "encerrada" | "cancelada";
  iniciada_em: string | null;
  encerrada_em: string | null;
  limite_pessoas: number | null;
  pico_pessoas: number;
  convite_hash: string | null;
  gravacao_status: string;
  gravacao_bytes: number | null;
  gravacao_segundos: number | null;
  criado_em: string;
};

export type SaldoDeLives = {
  competencia: string;
  gratis_por_mes: number;
  gratis_pessoas: number;
  gratis_usadas: number;
  gratis_restantes: number;
  /* Cortesia (live_cortesias): sem limite de transmissões no mês. */
  ilimitado?: boolean;
  /* Cortesia com pessoas vazio (migration 203): sem limite de pessoas; gratis_pessoas vem 100000. */
  pessoas_sem_limite?: boolean;
  pacotes: Array<{ compra_id: string; nome: string; lives: number; pessoas: number; usadas: number; restantes: number; valida_ate: string }>;
};

export async function saldoDeLives(admin: Admin, lojaId: string): Promise<SaldoDeLives> {
  const { data, error } = await admin.rpc("live_saldo", { p_empresa: lojaId });
  if (error || !data) throw new ErroDigital("Não foi possível ler as lives do mês.", 500);
  return data as SaldoDeLives;
}

export async function lerLive(admin: Admin, lojaId: string, id: string): Promise<LiveNoBanco> {
  if (!/^[0-9a-f-]{36}$/i.test(id)) throw new ErroDigital("Live não encontrada.", 404);
  const { data } = await admin.from("live_salas").select(CAMPOS_DA_LIVE).eq("id", id).eq("user_id", lojaId).maybeSingle();
  if (!data) throw new ErroDigital("Live não encontrada.", 404);
  return data as LiveNoBanco;
}

/* A vaga escolhida (pacote) precisa ser desta empresa, paga e com live sobrando. */
async function conferirVaga(admin: Admin, lojaId: string, dados: DadosDaLive, salaId: string | null) {
  if (dados.vaga !== "pacote" || !dados.vagaCompraId) return;
  const saldo = await saldoDeLives(admin, lojaId);
  const pacote = saldo.pacotes.find((p) => p.compra_id === dados.vagaCompraId);
  if (pacote) return;
  /* Esta live já usou a vaga deste pacote (foi ao ar): continua valendo. */
  if (salaId) {
    const { data } = await admin.from("live_usos").select("compra_id").eq("sala_id", salaId).maybeSingle();
    if ((data as { compra_id?: string | null } | null)?.compra_id === dados.vagaCompraId) return;
  }
  throw new ErroDigital("Este pacote KtaloguLive acabou ou venceu. Escolha outra vaga.", 400);
}

function configDoProduto(dados: DadosDaLive, anterior?: unknown) {
  const base = anterior ? lerConfigDigital(anterior, "live") : lerConfigDigital({ tipo: "live" }, "live");
  return { ...base, tipo: "live" as const, resumo: dados.aprender[0] ?? base.resumo ?? null };
}

export async function criarLive(admin: Admin, lojaId: string, dados: DadosDaLive) {
  await conferirVaga(admin, lojaId, dados, null);
  /* Reunião: só a sala (sem produto, nunca aparece na loja). O link já nasce pronto. */
  if (dados.categoria === "reuniao") {
    const { data: sala, error } = await admin
      .from("live_salas")
      .insert({ user_id: lojaId, product_id: null, categoria: "reuniao", ...salaParaOBanco(dados) })
      .select("id,codigo")
      .single();
    if (error || !sala) throw new ErroDigital("Não foi possível criar a reunião.", 500);
    return { id: (sala as { id: string }).id, produtoId: null as string | null };
  }
  const { data: produto, error } = await admin
    .from("products")
    .insert({
      name: dados.titulo,
      price: dados.preco,
      user_id: lojaId,
      product_kind: "digital",
      digital_config: configDoProduto(dados),
      image_url: dados.capa,
      description: dados.descricao,
      show_in_catalog: false,
      active: true,
    })
    .select("id")
    .single();
  if (error || !produto) throw new ErroDigital("Não foi possível criar a live.", 500);
  const produtoId = (produto as { id: string }).id;

  const { data: sala, error: erroDaSala } = await admin
    .from("live_salas")
    .insert({ user_id: lojaId, product_id: produtoId, categoria: "venda", ...salaParaOBanco(dados) })
    .select("id")
    .single();
  if (erroDaSala || !sala) {
    await admin.from("products").delete().eq("id", produtoId).eq("user_id", lojaId);
    throw new ErroDigital("Não foi possível criar a live.", 500);
  }
  const salaId = (sala as { id: string }).id;
  const { error: erroDoAcesso } = await admin
    .from("digital_deliverables")
    .insert({ user_id: lojaId, product_id: produtoId, tipo: "live", titulo: "Vaga na live", live_id: salaId, posicao: 0, ativo: true });
  if (erroDoAcesso) {
    await admin.from("live_salas").delete().eq("id", salaId);
    await admin.from("products").delete().eq("id", produtoId).eq("user_id", lojaId);
    throw new ErroDigital("Não foi possível preparar o acesso da live.", 500);
  }
  return { id: salaId, produtoId: produtoId as string | null };
}

export async function atualizarLive(admin: Admin, lojaId: string, live: LiveNoBanco, dados: DadosDaLive) {
  /* Depois de ir ao ar, a vaga já foi gasta: não troca mais. */
  /* A categoria nasce com a live (a venda tem produto, a reunião não): não troca depois. */
  const mesmaCategoria = dados.categoria === live.categoria ? dados : { ...dados, categoria: live.categoria };
  const dadosFinais = live.status === "agendada" ? mesmaCategoria : { ...mesmaCategoria, vaga: live.vaga, vagaCompraId: live.vaga_compra_id };
  if (live.status === "agendada") await conferirVaga(admin, lojaId, dadosFinais, live.id);
  const mudouOHorario = new Date(live.comeca_em).getTime() !== new Date(dadosFinais.comecaEm).getTime();
  const { error } = await admin
    .from("live_salas")
    .update({
      ...salaParaOBanco(dadosFinais),
      /* Mudou o dia: os lembretes saem de novo no horário novo. */
      ...(mudouOHorario ? { lembrete_dia_em: null, lembrete_15_em: null } : {}),
      atualizado_em: new Date().toISOString(),
    })
    .eq("id", live.id)
    .eq("user_id", lojaId);
  if (error) throw new ErroDigital("Não foi possível salvar a live.", 500);
  if (live.product_id) {
    const { data: produto } = await admin.from("products").select("digital_config").eq("id", live.product_id).eq("user_id", lojaId).maybeSingle();
    await admin
      .from("products")
      .update({
        name: dadosFinais.titulo,
        price: dadosFinais.preco,
        image_url: dadosFinais.capa,
        description: dadosFinais.descricao,
        digital_config: configDoProduto(dadosFinais, (produto as { digital_config?: unknown } | null)?.digital_config),
      })
      .eq("id", live.product_id)
      .eq("user_id", lojaId);
  }
}

/* Pôr ou tirar da vitrine da loja. */
export async function publicarLive(admin: Admin, lojaId: string, live: LiveNoBanco, naLoja: boolean, situacao: string) {
  if (live.categoria === "reuniao") throw new ErroDigital("Reunião não aparece na loja: mande o link para quem vai participar.", 400);
  if (!live.product_id) throw new ErroDigital("Esta live perdeu o produto. Crie outra.", 400);
  if (naLoja && live.status === "cancelada") throw new ErroDigital("Esta live foi cancelada.", 400);
  if (naLoja && situacao === "teste" && live.acesso !== "paga") {
    throw new ErroDigital("No período de teste, a live grátis fica fora da loja. Assine o Ktalogu para publicar.", 402);
  }
  await admin.from("products").update({ show_in_catalog: naLoja, active: true }).eq("id", live.product_id).eq("user_id", lojaId);
}

export async function cancelarLive(admin: Admin, lojaId: string, live: LiveNoBanco) {
  if (live.status === "ao_vivo") throw new ErroDigital("A live está no ar. Encerre pela sala antes de cancelar.", 400);
  if (live.status === "encerrada") throw new ErroDigital("Esta live já aconteceu.", 400);
  await admin.from("live_salas").update({ status: "cancelada", atualizado_em: new Date().toISOString() }).eq("id", live.id).eq("user_id", lojaId);
  if (live.product_id) await admin.from("products").update({ show_in_catalog: false }).eq("id", live.product_id).eq("user_id", lojaId);
}

export async function baseDaLoja(admin: Admin, lojaId: string): Promise<string> {
  const loja = await lerLojaDosDigitais(admin, lojaId);
  const base = baseDaLojaSemPedido(loja, process.env);
  if (!base) throw new ErroDigital("A loja ainda não tem endereço público.", 400);
  return base;
}

/*
 * O link curto, como no Meet: ktalogu.com.br/abc-defg-hij, ou no domínio da
 * loja (espiaelasatelier.com.br/abc-defg-hij). A loja reescreve para a sala.
 */
export function linkCurto(base: string, codigo: string): string {
  try {
    return `${new URL(base).origin}/${codigo}`;
  } catch {
    return `${base.replace(/\/+$/, "")}/${codigo}`;
  }
}

/* O link de quem apresenta: vale 12 horas, gerado a cada clique em "Entrar ao vivo". */
export async function linkDeQuemApresenta(admin: Admin, lojaId: string, live: LiveNoBanco): Promise<string> {
  const chave = chaveDoApresentador(live.id);
  if (!chave) throw new ErroDigital("O servidor não está pronto para lives.", 503);
  return `${linkCurto(await baseDaLoja(admin, lojaId), live.codigo)}?apresentar=${encodeURIComponent(chave)}`;
}

/* Um convite novo para apresentar junto (o anterior deixa de valer). */
export async function novoConvite(admin: Admin, lojaId: string, live: LiveNoBanco): Promise<string> {
  const token = randomBytes(24).toString("hex");
  const hash = createHash("sha256").update(token, "utf8").digest("hex");
  await admin.from("live_salas").update({ convite_hash: hash }).eq("id", live.id).eq("user_id", lojaId);
  return `${linkCurto(await baseDaLoja(admin, lojaId), live.codigo)}?convite=${token}`;
}

export async function linkDaLive(admin: Admin, lojaId: string, live: LiveNoBanco): Promise<{ sala: string; pagina: string | null }> {
  const base = await baseDaLoja(admin, lojaId);
  return { sala: linkCurto(base, live.codigo), pagina: live.product_id && live.categoria !== "reuniao" ? `${base}/p/${live.product_id}` : null };
}

export function itensDaLive(live: Pick<LiveNoBanco, "itens">): ItemParaSoltar[] {
  return (Array.isArray(live.itens) ? live.itens : []).map(lerItem).filter((i): i is ItemParaSoltar => Boolean(i));
}

/* A live como a tela do painel mostra. */
export function liveParaATela(
  live: LiveNoBanco,
  extras: { vagas: number; naLoja: boolean; preco: number; capa: string | null; descricao: string | null; base?: string | null },
) {
  return {
    id: live.id,
    categoria: live.categoria,
    codigo: live.codigo,
    link: extras.base ? linkCurto(extras.base, live.codigo) : null,
    gravacaoModo: live.gravacao_modo,
    produtoId: live.product_id,
    titulo: live.titulo,
    comecaEm: live.comeca_em,
    duracaoMin: live.duracao_min,
    esperaMin: live.espera_min,
    acesso: live.acesso,
    preco: extras.preco,
    venderDurante: live.vender_durante,
    limiteVagas: live.limite_vagas,
    participacao: live.participacao,
    camerasAlunos: live.cameras_alunos,
    chat: live.chat,
    gravar: live.gravar,
    gravacaoPara: live.gravacao_para,
    vaga: live.vaga,
    vagaCompraId: live.vaga_compra_id,
    itens: itensDaLive(live),
    vitrine: live.vitrine,
    produtosVitrine: live.produtos_vitrine ?? [],
    apresentadorNome: live.apresentador_nome,
    apresentadorBio: live.apresentador_bio,
    apresentadorFoto: live.apresentador_foto,
    aprender: (Array.isArray(live.aprender) ? live.aprender : []).filter((l): l is string => typeof l === "string"),
    capa: extras.capa,
    descricao: extras.descricao,
    status: live.status,
    situacao: situacaoDaLive(live),
    iniciadaEm: live.iniciada_em,
    encerradaEm: live.encerrada_em,
    picoPessoas: live.pico_pessoas,
    limitePessoas: live.limite_pessoas,
    temConvite: Boolean(live.convite_hash),
    gravacao: { status: live.gravacao_status, bytes: Number(live.gravacao_bytes ?? 0), segundos: Number(live.gravacao_segundos ?? 0) },
    vagas: extras.vagas,
    naLoja: extras.naLoja,
  };
}

export type LiveNaTela = ReturnType<typeof liveParaATela>;

/* Quantas vagas cada live já tem (acessos abertos à live). */
export async function vagasPorProduto(admin: Admin, lojaId: string, produtos: string[]): Promise<Map<string, number>> {
  const mapa = new Map<string, number>();
  if (!produtos.length) return mapa;
  const { data } = await admin
    .from("digital_entitlements")
    .select("product_id,status")
    .eq("user_id", lojaId)
    .in("product_id", produtos)
    .in("status", ["ativo", "agendado"])
    .limit(20000);
  for (const linha of (data ?? []) as Array<{ product_id: string }>) mapa.set(linha.product_id, (mapa.get(linha.product_id) ?? 0) + 1);
  return mapa;
}
