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

import { avisarLojaPorPush } from "@/lib/chats-push";

import { lerAjustesDaLoja } from "../ajustes";
import { novaChave, resumoDaChave } from "../compra";
import { tamanhoLegivel } from "../nome-do-arquivo";
import { dataCurtaDeBrasilia, lerConfigDigitalDaLoja } from "../produto";
import { configuracaoDoR2, enderecoParaBaixar } from "../r2";
import { linkDoArquivoMarcado, TETO_PARA_MARCAR } from "./link-marcado";

/*
 * As chaves do comprador e os acessos dele.
 *
 * Três jeitos de provar que o acesso é seu, todos presos a UM pedido ou a UM
 * cliente: a chave da compra (cookie do navegador que comprou), a chave da
 * entrega (o link do e-mail) e a sessão do portal (área de membros). Nenhum
 * deles abre o portal de outra pessoa.
 */

/* "entrar": a prévia da equipe ("Ver como aluno"), criada pelo painel. */
export type Finalidade = "compra" | "entrega" | "entrar";

const VALIDADE: Record<Finalidade, number> = {
  compra: 7 * 24 * 60 * 60 * 1000,
  /* O link do e-mail vale enquanto o acesso valer; a chave em si dura um ano. */
  entrega: 365 * 24 * 60 * 60 * 1000,
  entrar: 2 * 60 * 60 * 1000,
};

export async function criarChave(
  admin: SupabaseClient,
  params: { lojaId: string; clienteId: string | null; finalidade: Finalidade; catalogOrderId?: string | null; orderId?: string | null }
): Promise<string> {
  const chave = novaChave();
  const { error } = await admin.from("digital_access_keys").insert({
    user_id: params.lojaId,
    client_id: params.clienteId,
    finalidade: params.finalidade,
    catalog_order_id: params.catalogOrderId ?? null,
    order_id: params.orderId ?? null,
    hash: resumoDaChave(chave),
    expira_em: new Date(Date.now() + VALIDADE[params.finalidade]).toISOString(),
  });
  if (error) throw new Error("Não foi possível guardar a chave do acesso.");
  return chave;
}

export type ChaveConferida = {
  id: string;
  lojaId: string;
  clienteId: string | null;
  catalogOrderId: string | null;
  orderId: string | null;
};

export async function conferirChave(admin: SupabaseClient, chave: string, finalidade: Finalidade): Promise<ChaveConferida | null> {
  if (!chave || chave.length < 20 || chave.length > 200) return null;
  const { data } = await admin
    .from("digital_access_keys")
    .select("id,user_id,client_id,catalog_order_id,order_id,expira_em,revogada_em")
    .eq("hash", resumoDaChave(chave))
    .eq("finalidade", finalidade)
    .maybeSingle();
  const linha = data as {
    id: string;
    user_id: string;
    client_id: string | null;
    catalog_order_id: string | null;
    order_id: string | null;
    expira_em: string | null;
    revogada_em: string | null;
  } | null;
  if (!linha || linha.revogada_em) return null;
  if (linha.expira_em && new Date(linha.expira_em).getTime() < Date.now()) return null;
  void admin.from("digital_access_keys").update({ usada_em: new Date().toISOString() }).eq("id", linha.id);
  return { id: linha.id, lojaId: linha.user_id, clienteId: linha.client_id, catalogOrderId: linha.catalog_order_id, orderId: linha.order_id };
}

/* O pedido interno de uma compra da loja (nasce quando o pagamento é aprovado). */
export async function pedidoInternoDaCompra(admin: SupabaseClient, catalogOrderId: string): Promise<string | null> {
  const { data } = await admin.from("catalog_orders").select("approved_order_id").eq("id", catalogOrderId).maybeSingle();
  return ((data as { approved_order_id?: string | null } | null)?.approved_order_id as string | null) ?? null;
}

export type AcessoNaTela = {
  id: string;
  produtoId: string;
  produto: string;
  capa: string | null;
  tipo: "arquivo" | "link" | "texto" | "codigos" | "curso" | "encomenda" | "live";
  titulo: string;
  descricao: string | null;
  /* Só aparece quando o acesso está liberado. */
  instrucoes: string | null;
  arquivo: { nome: string; extensao: string; categoria: string; tamanhoLegivel: string } | null;
  situacao: "liberado" | "preparando" | "fechado";
  motivo: string | null;
  downloads: number;
  liberadoEm: string | null;
  expiraEm: string | null;
  /* Códigos e licenças: o código desta unidade (só com o acesso liberado). */
  codigo: string | null;
  /* O que ainda espera, para a tela dizer quando chega: o código (a loja está
     repondo), o arquivo feito sob encomenda (até a data prometida) ou a data
     da pré-venda. */
  espera: { tipo: "codigo" | "encomenda" | "pre_venda"; data: string | null } | null;
  /* Live: a sala, o dia e se tem gravação para quem garantiu. */
  live?: { salaId: string; comecaEm: string; status: string; gravacao: boolean } | null;
};

type ArquivoDaLinha = { nome: string; extensao: string; categoria: string; tamanho_bytes: number; status: string };

/* O motivo que o freio grava quando pausa (o mesmo do painel). */
export const MOTIVO_DO_FREIO = "freio: muitos lugares diferentes";

type LinhaDoAcesso = {
  id: string;
  status: string;
  motivo: string | null;
  expira_em: string | null;
  liberado_em: string | null;
  libera_em: string | null;
  entregar_ate: string | null;
  ultimo_acesso_em: string | null;
  downloads: number;
  limite_downloads: number | null;
  product_id: string;
  products: { name: string; image_url: string | null } | null;
  digital_deliverables: {
    tipo: AcessoNaTela["tipo"];
    titulo: string;
    descricao: string | null;
    instrucoes: string | null;
    posicao: number;
    digital_files: ArquivoDaLinha | null;
    live_salas?: { id: string; comeca_em: string; status: string; gravar: boolean; gravacao_para: string } | null;
  } | null;
  digital_codes: Array<{ codigo: string }> | { codigo: string } | null;
  arquivo_final: ArquivoDaLinha | null;
};

/* "arquivo_final:...": o arquivo feito sob encomenda para esta unidade (o
   entregável do produto tem o próprio arquivo, por outro caminho). */
const SELECT_DO_ACESSO =
  "id,status,motivo,expira_em,liberado_em,libera_em,entregar_ate,ultimo_acesso_em,downloads,limite_downloads,product_id,products(name,image_url),digital_deliverables(tipo,titulo,descricao,instrucoes,posicao,digital_files(nome,extensao,categoria,tamanho_bytes,status),live_salas(id,comeca_em,status,gravar,gravacao_para)),digital_codes(codigo),arquivo_final:digital_files!digital_entitlements_arquivo_final_id_fkey(nome,extensao,categoria,tamanho_bytes,status)";

/* "2026-10-14" → "14/10" (a data do calendário, sem fuso). */
function diaDoCalendario(data: string): string {
  const [, mes, dia] = data.slice(0, 10).split("-");
  return dia && mes ? `${dia}/${mes}` : data;
}

/* O que a unidade ainda espera. */
function esperaDaLinha(linha: LinhaDoAcesso): AcessoNaTela["espera"] {
  if (linha.status === "aguardando_codigo") return { tipo: "codigo", data: null };
  if (linha.status === "aguardando_arquivo" && linha.digital_deliverables?.tipo === "encomenda") return { tipo: "encomenda", data: linha.entregar_ate };
  if (linha.status === "agendado") return { tipo: "pre_venda", data: linha.libera_em };
  return null;
}

/* O aviso de quem espera, com a data quando há. */
function motivoDaEspera(espera: NonNullable<AcessoNaTela["espera"]>): string {
  if (espera.tipo === "codigo") return "Seu código chega em breve: a loja está repondo. Ele aparece aqui e vai para o seu e-mail.";
  if (espera.tipo === "encomenda") {
    return espera.data
      ? `Em produção: a loja entrega até ${diaDoCalendario(espera.data)}. Aparece aqui e vai para o seu e-mail.`
      : "Em produção: aparece aqui e vai para o seu e-mail quando ficar pronto.";
  }
  return espera.data ? `Pré-venda: libera em ${dataCurtaDeBrasilia(espera.data)}. Você recebe por e-mail.` : "Pré-venda: libera na data do lançamento.";
}

export function situacaoDoAcesso(linha: { status: string; expira_em: string | null; motivo?: string | null }): { situacao: AcessoNaTela["situacao"]; motivo: string | null } {
  if (linha.status === "ativo") {
    if (linha.expira_em && new Date(linha.expira_em).getTime() < Date.now()) return { situacao: "fechado", motivo: "O prazo deste acesso terminou." };
    return { situacao: "liberado", motivo: null };
  }
  if (linha.status === "aguardando_arquivo" || linha.status === "agendado" || linha.status === "aguardando_codigo") {
    return { situacao: "preparando", motivo: "A loja está terminando de preparar este item. Ele aparece aqui sozinho." };
  }
  if (linha.status === "suspenso") return { situacao: "fechado", motivo: "Acesso suspenso: o pagamento não foi confirmado ou foi devolvido." };
  if (linha.status === "revogado") return { situacao: "fechado", motivo: "Este pedido foi cancelado." };
  if (linha.status === "expirado") return { situacao: "fechado", motivo: "O prazo deste acesso terminou." };
  if (linha.status === "pausado" && (linha.motivo ?? "").startsWith("freio")) {
    return { situacao: "fechado", motivo: "Acesso pausado: ele foi aberto em muitos lugares diferentes. Fale com a loja para reativar." };
  }
  return { situacao: "fechado", motivo: "Acesso pausado pela loja. Fale com ela." };
}

function paraATela(linha: LinhaDoAcesso): AcessoNaTela {
  const { situacao, motivo: motivoGeral } = situacaoDoAcesso(linha);
  const entregavel = linha.digital_deliverables;
  const espera = situacao === "preparando" ? esperaDaLinha(linha) : null;
  const motivo = espera ? motivoDaEspera(espera) : motivoGeral;
  /* Sob encomenda: o arquivo é o feito para esta unidade. */
  const arquivo = entregavel?.tipo === "encomenda" ? (situacao === "liberado" ? linha.arquivo_final : null) : entregavel?.digital_files;
  const codigos = Array.isArray(linha.digital_codes) ? linha.digital_codes : linha.digital_codes ? [linha.digital_codes] : [];
  return {
    id: linha.id,
    produtoId: linha.product_id,
    produto: linha.products?.name ?? "Produto",
    capa: linha.products?.image_url ?? null,
    tipo: entregavel?.tipo ?? "texto",
    titulo: entregavel?.titulo ?? "Item",
    descricao: entregavel?.descricao ?? null,
    instrucoes: situacao === "liberado" ? entregavel?.instrucoes ?? null : null,
    arquivo: arquivo
      ? { nome: arquivo.nome, extensao: arquivo.extensao, categoria: arquivo.categoria, tamanhoLegivel: tamanhoLegivel(Number(arquivo.tamanho_bytes) || 0) }
      : null,
    situacao,
    motivo,
    downloads: linha.downloads,
    liberadoEm: linha.liberado_em,
    expiraEm: linha.expira_em,
    codigo: situacao === "liberado" && entregavel?.tipo === "codigos" ? codigos[0]?.codigo ?? null : null,
    espera,
    live:
      entregavel?.tipo === "live" && entregavel.live_salas
        ? {
            salaId: entregavel.live_salas.id,
            comecaEm: entregavel.live_salas.comeca_em,
            status: entregavel.live_salas.status,
            gravacao: entregavel.live_salas.gravar && entregavel.live_salas.gravacao_para === "compradores",
          }
        : null,
  };
}

/*
 * O código e o texto com instruções aparecem direto na tela: ver já é usar.
 * Marca a primeira vez, e o lembrete de 24 h ("você ainda não abriu") não
 * sai para quem já viu o seu código.
 */
async function marcarOQueApareceuNaTela(admin: SupabaseClient, linhas: LinhaDoAcesso[]) {
  const vistos = linhas
    .filter((linha) => !linha.ultimo_acesso_em && ["codigos", "texto"].includes(linha.digital_deliverables?.tipo ?? ""))
    .filter((linha) => situacaoDoAcesso(linha).situacao === "liberado")
    .map((linha) => linha.id);
  if (!vistos.length) return;
  await admin.from("digital_entitlements").update({ ultimo_acesso_em: new Date().toISOString() }).in("id", vistos).is("ultimo_acesso_em", null);
}

export async function acessosDoPedido(admin: SupabaseClient, orderId: string): Promise<AcessoNaTela[]> {
  const { data } = await admin.from("digital_entitlements").select(SELECT_DO_ACESSO).eq("order_id", orderId).order("criado_em", { ascending: true });
  const linhas = (data ?? []) as unknown as LinhaDoAcesso[];
  await marcarOQueApareceuNaTela(admin, linhas).catch(() => undefined);
  return linhas.sort((a, b) => (a.digital_deliverables?.posicao ?? 0) - (b.digital_deliverables?.posicao ?? 0)).map(paraATela);
}

export async function acessosDoCliente(admin: SupabaseClient, lojaId: string, clienteId: string): Promise<AcessoNaTela[]> {
  const { data } = await admin
    .from("digital_entitlements")
    .select(SELECT_DO_ACESSO)
    .eq("user_id", lojaId)
    .eq("client_id", clienteId)
    .order("criado_em", { ascending: false })
    .limit(300);
  const linhas = (data ?? []) as unknown as LinhaDoAcesso[];
  await marcarOQueApareceuNaTela(admin, linhas).catch(() => undefined);
  return linhas.map(paraATela);
}

export type AcessoParaUsar = {
  id: string;
  user_id: string;
  client_id: string;
  order_id: string | null;
  catalog_order_id: string | null;
  status: string;
  motivo: string | null;
  expira_em: string | null;
  downloads: number;
  limite_downloads: number | null;
  digital_deliverables: {
    tipo: string;
    url: string | null;
    digital_files: ArquivoParaUsar | null;
  } | null;
  /* Sob encomenda: o arquivo feito para esta unidade. */
  arquivo_final: ArquivoParaUsar | null;
  /* O limite de downloads do produto (quando o acesso não tem o seu). */
  products: { name: string | null; digital_config: unknown } | null;
};

type ArquivoParaUsar = { storage_key: string; nome: string; mime: string | null; status: string; tamanho_bytes: number | null };

/* O arquivo que este acesso entrega (o feito sob encomenda, ou o do produto). */
export function arquivoDoAcesso(acesso: AcessoParaUsar): ArquivoParaUsar | null {
  const entregavel = acesso.digital_deliverables;
  if (!entregavel) return null;
  return entregavel.tipo === "encomenda" ? acesso.arquivo_final : entregavel.digital_files;
}

/* Lê um acesso para usar (baixar, abrir o link), já com o segredo dele. */
export async function lerAcessoParaUsar(admin: SupabaseClient, acessoId: string): Promise<AcessoParaUsar | null> {
  if (!/^[0-9a-f-]{36}$/i.test(acessoId)) return null;
  const { data } = await admin
    .from("digital_entitlements")
    .select(
      "id,user_id,client_id,order_id,catalog_order_id,status,motivo,expira_em,downloads,limite_downloads,digital_deliverables(tipo,url,digital_files(storage_key,nome,mime,status,tamanho_bytes)),arquivo_final:digital_files!digital_entitlements_arquivo_final_id_fkey(storage_key,nome,mime,status,tamanho_bytes),products(name,digital_config)"
    )
    .eq("id", acessoId)
    .maybeSingle();
  return (data as unknown as AcessoParaUsar | null) ?? null;
}

export type UsoDoAcesso = { ok: true; url: string } | { ok: false; motivo: string; status: number };

/**
 * O freio de compartilhamento: o mesmo pedido aberto em mais lugares do que a
 * loja permite em 24 horas (desde a última reativação) pausa os acessos dele
 * e avisa a equipe no celular. Devolve quantos lugares contou, quando pausou.
 */
async function puxarOFreio(
  admin: SupabaseClient,
  acesso: AcessoParaUsar,
  limite: number | null
): Promise<number | null> {
  if (!limite || !acesso.order_id) return null;
  const { data: irmaos } = await admin
    .from("digital_entitlements")
    .select("id,freio_liberado_em")
    .eq("user_id", acesso.user_id)
    .eq("order_id", acesso.order_id)
    .limit(200);
  const linhas = (irmaos ?? []) as Array<{ id: string; freio_liberado_em: string | null }>;
  if (!linhas.length) return null;
  const reativadoEm = Math.max(0, ...linhas.map((l) => (l.freio_liberado_em ? new Date(l.freio_liberado_em).getTime() : 0)));
  const desde = new Date(Math.max(Date.now() - 24 * 60 * 60 * 1000, reativadoEm)).toISOString();
  const { data: eventos } = await admin
    .from("digital_download_events")
    .select("rede")
    .in(
      "entitlement_id",
      linhas.map((l) => l.id)
    )
    .gte("criado_em", desde)
    .not("rede", "is", null)
    .limit(2000);
  const lugares = new Set(((eventos ?? []) as Array<{ rede: string }>).map((e) => e.rede)).size;
  if (lugares <= limite) return null;
  const { data: pausados } = await admin
    .from("digital_entitlements")
    .update({ status: "pausado", motivo: MOTIVO_DO_FREIO, atualizado_em: new Date().toISOString() })
    .eq("user_id", acesso.user_id)
    .eq("order_id", acesso.order_id)
    .eq("status", "ativo")
    .select("id");
  if ((pausados ?? []).length) {
    await avisarLojaPorPush(admin, {
      tenantId: acesso.user_id,
      titulo: "Acesso pausado pelo freio",
      corpo: `${acesso.products?.name?.trim() || "Um produto digital"} foi aberto em ${lugares} lugares em 24 horas. Veja em Entregas para reativar.`,
      rota: "/dashboard/digitais/entregas",
      tag: `ktalogu-freio-${acesso.order_id}`,
    }).catch(() => undefined);
  }
  return lugares;
}

/*
 * Usar um acesso: confere tudo e devolve para onde mandar o comprador.
 * Arquivo: endereço assinado de 15 minutos. Link: o endereço guardado pela
 * loja (passando por aqui, dá para cortar). Toda vez fica registrado; vários
 * cliques no mesmo arquivo em 10 minutos contam como um download.
 */
export async function usarAcesso(
  admin: SupabaseClient,
  acesso: AcessoParaUsar,
  origem: { ip: string | null; navegador: string | null; pais: string | null }
): Promise<UsoDoAcesso> {
  const { situacao, motivo } = situacaoDoAcesso(acesso);
  if (situacao !== "liberado") return { ok: false, motivo: motivo ?? "Acesso fechado.", status: 403 };
  const entregavel = acesso.digital_deliverables;
  if (!entregavel) return { ok: false, motivo: "Este item não existe mais.", status: 404 };

  const { data: dadosDosAjustes } = await admin.from("digital_settings").select("config").eq("user_id", acesso.user_id).maybeSingle();
  const ajustes = lerAjustesDaLoja((dadosDosAjustes as { config?: unknown } | null)?.config);

  let url: string;
  let tipo: "download" | "link";
  if (entregavel.tipo === "arquivo" || entregavel.tipo === "encomenda") {
    const arquivo = arquivoDoAcesso(acesso);
    if (!arquivo || arquivo.status !== "pronto") return { ok: false, motivo: "O arquivo ainda está sendo preparado pela loja.", status: 409 };
    /* O limite do acesso (a loja mudou para este comprador) vence o do produto. */
    const limite = acesso.limite_downloads ?? lerConfigDigitalDaLoja(acesso.products?.digital_config).limiteDeDownloads;
    if (limite && acesso.downloads >= limite) {
      return {
        ok: false,
        motivo: `Você já baixou ${limite === 1 ? "a vez" : `as ${limite} vezes`} que este produto permite. Fale com a loja se precisar de novo.`,
        status: 403,
      };
    }
    const cfg = configuracaoDoR2();
    if (!cfg) return { ok: false, motivo: "Os downloads estão indisponíveis agora. Tente em alguns minutos.", status: 503 };
    /* A marca no PDF: do produto ou, sem escolha nele, a das Configurações. */
    const ehPdf = /pdf/i.test(arquivo.mime ?? "") || /\.pdf$/i.test(arquivo.nome);
    const marcar =
      ehPdf &&
      (lerConfigDigitalDaLoja(acesso.products?.digital_config).marcaNoPdf ?? ajustes.protecao.marcaDagua) &&
      Number(arquivo.tamanho_bytes ?? Infinity) <= TETO_PARA_MARCAR;
    url = marcar ? linkDoArquivoMarcado(acesso.id) : enderecoParaBaixar(cfg, arquivo.storage_key, { nome: arquivo.nome, segundos: 900, tipo: arquivo.mime });
    tipo = "download";
  } else if (entregavel.tipo === "link" && entregavel.url) {
    url = entregavel.url;
    tipo = "link";
  } else {
    return { ok: false, motivo: "Este item não se baixa: o conteúdo está na tela.", status: 400 };
  }

  const agora = new Date();
  const dezMinutos = new Date(agora.getTime() - 10 * 60 * 1000).toISOString();
  const { count } = await admin
    .from("digital_download_events")
    .select("id", { count: "exact", head: true })
    .eq("entitlement_id", acesso.id)
    .eq("tipo", tipo)
    .gte("criado_em", dezMinutos);
  await admin.from("digital_download_events").insert({
    user_id: acesso.user_id,
    entitlement_id: acesso.id,
    client_id: acesso.client_id,
    tipo,
    ip: origem.ip?.slice(0, 64) ?? null,
    rede: redeDoIp(origem.ip),
    pais: origem.pais?.slice(0, 8) ?? null,
    navegador: origem.navegador?.slice(0, 200) ?? null,
  });
  await admin
    .from("digital_entitlements")
    .update({
      ultimo_acesso_em: agora.toISOString(),
      ...(tipo === "download" && (count ?? 0) === 0 ? { downloads: acesso.downloads + 1 } : {}),
    })
    .eq("id", acesso.id);

  /* O freio conta este lugar também; passou do limite, este download já não sai. */
  const freio = await puxarOFreio(admin, acesso, ajustes.protecao.freioLugares).catch((erro) => {
    console.error("[digitais] freio", erro);
    return null;
  });
  if (freio) return { ok: false, motivo: "Este acesso foi aberto em muitos lugares diferentes e foi pausado. Fale com a loja para reativar.", status: 403 };
  return { ok: true, url };
}

/* A "rede" de um IP: os três primeiros blocos do IPv4 ou os quatro do IPv6. */
export function redeDoIp(ip: string | null | undefined): string | null {
  const valor = String(ip || "").trim();
  if (!valor) return null;
  if (valor.includes(":")) return valor.split(":").slice(0, 4).join(":");
  const partes = valor.split(".");
  return partes.length === 4 ? partes.slice(0, 3).join(".") : null;
}

export function origemDoPedido(request: Request) {
  const cabecalhos = request.headers;
  const ip = (cabecalhos.get("cf-connecting-ip") || cabecalhos.get("x-forwarded-for")?.split(",")[0] || "").trim() || null;
  return { ip, navegador: cabecalhos.get("user-agent"), pais: cabecalhos.get("cf-ipcountry") };
}
