import sharp from "sharp";

import type { CompanyPermission } from "@/lib/company-context";
import { digitaisLiberadosAgora } from "@/lib/digitais/liberacao-do-banco";
import { ErroDigital, type ContextoDigital } from "@/lib/digitais/servidor/contexto";
import { assinaturaDaEmpresa } from "@/lib/fiscal/servidor/assinatura-da-empresa";

import { gerarCodigoDeEnvio } from "../codigo-de-envio";
import { contatosNovos, recusaDeContatos } from "../contatos";
import type { ContextoDoAssistente } from "../contexto";
import { arquivoDoCaminho, ehEnvioDigital } from "../envio-digital";
import { guardarImagemDaLoja, receberImagem } from "../imagens";
import { exigir, falha, pode, recusar } from "./comum";
import { lerReferenciaDeArquivo } from "./digitais-regras";

/*
 * O que todas as ferramentas de Produtos digitais fazem antes de tudo, igual
 * às rotas do painel (lib/digitais/servidor/contexto.ts): a área liberada
 * para a empresa (variável ou banco), a permissão EXATA da rota e a
 * assinatura da empresa em dia. Depois, o mesmo contexto que as rotas usam
 * (`ContextoDigital`), e as funções das rotas (lib/digitais/servidor/acoes)
 * fazem o trabalho: nada de banco ou depósito reescrito aqui.
 */

const MENSAGEM_DA_ASSINATURA: Record<string, string> = {
  expirada: "A assinatura do Ktalogu desta empresa venceu. Renove em Assinatura para voltar a usar os produtos digitais.",
};

export async function abrirDigital(ctx: ContextoDoAssistente, permissao: CompanyPermission, oQue: string): Promise<ContextoDigital> {
  if (!(await digitaisLiberadosAgora([ctx.chave.companyId, ctx.empresa.ownerId, ctx.donoDosDados]))) {
    recusar("A área de Produtos digitais (com as lives) ainda não está liberada para esta empresa. Quem libera é a equipe da Ktalogu; enquanto isso, estes comandos não funcionam.");
  }
  exigir(ctx, permissao, oQue);
  const assinatura = await assinaturaDaEmpresa(ctx.admin, ctx.empresa.ownerId || ctx.user.id);
  if (!assinatura.ativa) {
    recusar(
      MENSAGEM_DA_ASSINATURA[assinatura.situacao] ??
        "A assinatura do Ktalogu desta empresa está inativa. Regularize em Assinatura para usar os produtos digitais."
    );
  }
  return {
    admin: ctx.admin,
    user: ctx.user,
    ctx: ctx.empresa,
    lojaId: ctx.donoDosDados,
    pode: (chave) => pode(ctx, chave),
    situacao: assinatura.situacao,
    liberado: true,
  };
}

/** O pedido que as funções das rotas recebem: só serve ao aviso da loja e ao endereço público. */
export function pedidoDoAssistente(ctx: ContextoDoAssistente) {
  return new Request(`${ctx.origemDoAdmin.replace(/\/$/, "")}/api/mcp`);
}

/** Roda uma função das rotas e traduz o erro dela para o assistente (com a lista do que falta, quando houver). */
export async function naRota<T>(fazer: () => Promise<T>): Promise<T> {
  try {
    return await fazer();
  } catch (erro) {
    if (erro instanceof ErroDigital) {
      const faltas = Array.isArray(erro.extra?.faltas) ? (erro.extra.faltas as string[]) : [];
      recusar(faltas.length ? `${erro.message} O que falta: ${faltas.join(" ")}` : erro.message);
    }
    throw erro;
  }
}

/** Texto que o cliente lê: o assistente não escreve contato novo (ver contatos.ts). */
export function semContatoNovo(antes: string | null | undefined, depois: string | null | undefined, onde: string) {
  const novos = contatosNovos(antes ?? null, depois ?? null);
  if (novos.length) recusar(recusaDeContatos(novos, onde));
}

/* ------------------------------------------------------------ arquivos */

/**
 * O arquivo da biblioteca que a pessoa indicou: o id (de listar_arquivos_digitais)
 * ou o código de um envio de pedir_video / pedir_arquivo_digital. Devolve o
 * id do arquivo na biblioteca, conferido como sendo desta loja.
 */
export async function arquivoIndicado(ctx: ContextoDoAssistente, contexto: ContextoDigital, valor: unknown): Promise<{ id: string; nome: string; status: string; categoria: string }> {
  const referencia = lerReferenciaDeArquivo(valor);
  if (!referencia) recusar('Arquivo não reconhecido. Use o id que voltou em listar_arquivos_digitais, ou o código "envio:..." de pedir_video / pedir_arquivo_digital.');
  let id: string;
  if (referencia.tipo === "id") {
    id = referencia.id;
  } else {
    const { data, error } = await ctx.admin
      .from("assistentes_envios")
      .select("tipo,caminho,recebido_em,vence_em")
      .eq("codigo", referencia.codigo)
      .eq("company_id", ctx.chave.companyId)
      .maybeSingle();
    if (error) falha("ler o envio", error);
    const envio = data as { tipo: string | null; caminho: string | null; recebido_em: string | null; vence_em: string } | null;
    if (!envio) recusar("Este código de envio não existe nesta conta. Chame pedir_video (ou pedir_arquivo_digital) para gerar outro link.");
    if (!ehEnvioDigital(envio.tipo)) recusar("Este código é de uma imagem. Para vídeo ou arquivo dos produtos digitais, chame pedir_video ou pedir_arquivo_digital.");
    const doEnvio = arquivoDoCaminho(envio.caminho);
    if (!envio.recebido_em || !doEnvio) {
      recusar("O arquivo ainda não terminou de chegar por esse link. Peça à pessoa para enviar e esperar a página dizer \"Pronto!\"; confira com ver_envio_digital.");
    }
    id = doEnvio;
  }
  const { data, error } = await contexto.admin
    .from("digital_files")
    .select("id,nome,status,categoria")
    .eq("id", id)
    .eq("user_id", contexto.lojaId)
    .maybeSingle();
  if (error) falha("ler o arquivo", error);
  const arquivo = data as { id: string; nome: string; status: string; categoria: string } | null;
  if (!arquivo || arquivo.status === "apagado") recusar("Arquivo não encontrado na biblioteca da loja. Use listar_arquivos_digitais.");
  return arquivo;
}

/* O link curto de 24 horas (/baixar/<código>) de um arquivo do depósito dos
   digitais. A rota /baixar confere de novo, no clique, se o arquivo ainda
   está pronto e é desta empresa; o endereço assinado nunca vai à conversa. */
const LINK_VALE_POR_MS = 24 * 60 * 60 * 1000;

export async function linkCurtoDoArquivo(ctx: ContextoDoAssistente, arquivo: { id: string; nome: string }) {
  const valeAte = new Date(Date.now() + LINK_VALE_POR_MS);
  for (let tentativa = 0; tentativa < 3; tentativa++) {
    const codigo = gerarCodigoDeEnvio();
    const { error } = await ctx.admin.from("assistentes_links").insert({
      codigo,
      company_id: ctx.chave.companyId,
      caminho: `digital_file:${arquivo.id}`,
      nome: arquivo.nome.slice(0, 200),
      vence_em: valeAte.toISOString(),
    });
    if (!error) {
      const base = `${ctx.origemDoAdmin.replace(/\/$/, "")}/baixar/${codigo}`;
      return { baixar: base, assistir: `${base}?assistir=1`, vale_ate: valeAte.toISOString() };
    }
    if ((error as { code?: string }).code !== "23505") falha("preparar o link do arquivo", error);
  }
  recusar("Não deu para preparar o link agora. Tente de novo em instantes.");
}

export const COMO_MOSTRAR_O_LINK =
  "Mostre o link à pessoa exatamente como veio, letra por letra, sem encurtar nem reescrever. Vale 24 horas; depois, peça outro.";

/* ------------------------------------------------------------ imagens */

export type ImagemDigital = { conteudo: Buffer; tipo: string; extensao: string; largura: number; altura: number; origem: string };

/**
 * A imagem que vai para os produtos digitais (capa do produto, capa do
 * módulo, foto de quem apresenta, foto de destaque da área): recebida como
 * nas outras ferramentas (envio, link ou produto) e preparada como o recorte
 * da tela (quadrada nas capas e fotos; larga no destaque).
 */
export async function prepararImagemDigital(ctx: ContextoDoAssistente, valor: unknown, formato: "quadrada" | "larga" | "livre"): Promise<ImagemDigital> {
  const recebida = await receberImagem(ctx, valor, { minimo: 200 });
  let processo = sharp(recebida.conteudo, { limitInputPixels: 60_000_000, failOn: "error", animated: false }).rotate();
  if (formato === "quadrada") processo = processo.resize({ width: 1200, height: 1200, fit: "cover", position: "attention", withoutEnlargement: true });
  else if (formato === "larga") processo = processo.resize({ width: 1920, height: 1080, fit: "cover", position: "attention", withoutEnlargement: true });
  else processo = processo.resize({ width: 1600, height: 1600, fit: "inside", withoutEnlargement: true });
  const saida = await processo.webp({ quality: 85 }).toBuffer({ resolveWithObject: true });
  return { conteudo: saida.data, tipo: "image/webp", extensao: "webp", largura: saida.info.width, altura: saida.info.height, origem: recebida.origem };
}

/** Guarda no mesmo depósito e pasta da tela ("digitais/capas", "digitais/modulos"...). */
export function guardarImagemDigital(ctx: ContextoDoAssistente, imagem: ImagemDigital, pasta: string) {
  return guardarImagemDaLoja(ctx, imagem, pasta);
}
