import sharp from "sharp";

import { lerCodigoDeEnvio } from "./codigo-de-envio";
import type { ContextoDoAssistente } from "./contexto";
import { ErroDaFerramenta } from "./ferramentas/tipos";
import { buscar, ErroDeRede } from "./importacao/rede";

/*
 * Imagem que chega ao assistente (logo, banner, foto de seção, story...).
 *
 * O Claude e o ChatGPT NÃO repassam a imagem anexada na conversa: o servidor
 * nunca recebe o arquivo. Por isso toda ferramenta que pede imagem aceita um
 * destes três valores, e só eles:
 *
 * 1. Um código de envio, como "envio:Ab3dE9xK2mQp": a ferramenta
 *    `pedir_imagem` devolve um link curto (admin.ktalogu.com.br/enviar/...)
 *    onde a pessoa solta o arquivo do celular ou do computador; a imagem
 *    fica guardada no depósito privado (migration 192, assistentes_envios).
 * 2. Um link direto https:// de uma imagem na internet.
 * 3. "produto:<id>": a primeira foto de um produto da própria loja.
 *
 * `receberImagem` devolve os bytes conferidos (formato aceito, tamanho
 * mínimo), sem mexer em nada. Cada ferramenta prepara (sharp) do seu jeito.
 */

export type ImagemRecebida = {
  conteudo: Buffer;
  /* jpeg, png, webp, gif, avif, heif, tiff */
  formato: string;
  largura: number;
  altura: number;
  /* De onde veio, para a prévia: "imagem enviada por você", o link, ou o produto. */
  origem: string;
};

export const DESCRICAO_DO_CAMPO_DE_IMAGEM =
  'A imagem: o código "envio:..." devolvido por pedir_imagem (quando a pessoa anexou a imagem na conversa, chame pedir_imagem e mande o link para ela enviar o arquivo), um link direto https:// de uma imagem, ou "produto:<id>" para usar a foto de um produto da loja.';

const LIMITE_EM_BYTES = 15 * 1024 * 1024;
const FORMATOS = new Set(["jpeg", "png", "webp", "gif", "avif", "heif", "tiff"]);
const BALDE = "assistentes-arquivos";

function recusar(mensagem: string): never {
  throw new ErroDaFerramenta(mensagem);
}

export const CODIGO_DE_ENVIO = /^envio:([A-Za-z0-9]{12})$/;

async function bytesDoEnvio(ctx: ContextoDoAssistente, codigo: string): Promise<Buffer> {
  const { data, error } = await ctx.admin
    .from("assistentes_envios")
    .select("caminho,vence_em,recebido_em,tipo")
    .eq("codigo", codigo)
    .eq("company_id", ctx.chave.companyId)
    .maybeSingle();
  if (error) {
    console.error("[assistentes] ler envio", error.message);
    recusar("Não deu para ler a imagem enviada agora. Tente de novo em instantes.");
  }
  const envio = data as { caminho: string | null; vence_em: string; recebido_em: string | null; tipo: string | null } | null;
  if (!envio) recusar("Este código de envio não existe nesta conta. Chame pedir_imagem para gerar outro link.");
  if (envio.tipo?.startsWith("digital:")) recusar("Este código é de um vídeo ou arquivo dos produtos digitais, não de uma imagem. Para imagem, chame pedir_imagem.");
  if (!envio.recebido_em || !envio.caminho) {
    recusar("A imagem ainda não foi enviada por esse link. Peça à pessoa para abrir o link e enviar o arquivo, e tente de novo depois.");
  }
  if (new Date(envio.vence_em).getTime() <= Date.now()) recusar("Este envio venceu. Chame pedir_imagem para gerar outro link.");
  const { data: arquivo, error: erroDoArquivo } = await ctx.admin.storage.from(BALDE).download(envio.caminho);
  if (erroDoArquivo || !arquivo) recusar("A imagem enviada não está mais disponível. Chame pedir_imagem para gerar outro link.");
  return Buffer.from(await arquivo.arrayBuffer());
}

async function bytesDoProduto(ctx: ContextoDoAssistente, produtoId: string): Promise<{ corpo: Buffer; nome: string }> {
  if (!/^[0-9a-f-]{36}$/i.test(produtoId)) recusar('Use "produto:<id>" com o id do produto devolvido por buscar_produtos.');
  const { data, error } = await ctx.supabase
    .from("products")
    .select("name,image_url")
    .eq("id", produtoId)
    .in("user_id", ctx.tenantIds)
    .maybeSingle();
  if (error) recusar("Não deu para ler o produto agora. Tente de novo em instantes.");
  const produto = data as { name?: string | null; image_url?: string | null } | null;
  if (!produto) recusar("Produto não encontrado nesta loja. Use buscar_produtos para achar o id.");
  /* A capa do produto; sem ela, a primeira foto da galeria. */
  let endereco = typeof produto.image_url === "string" ? produto.image_url : "";
  if (!/^https:\/\//.test(endereco)) {
    const { data: fotos } = await ctx.supabase
      .from("product_images")
      .select("url,image_url,is_principal,is_primary,position,sort_order")
      .eq("product_id", produtoId)
      .order("position", { ascending: true })
      .limit(20);
    const lista = (fotos ?? []) as Array<{ url?: string | null; image_url?: string | null; is_principal?: boolean | null; is_primary?: boolean | null }>;
    const escolhida = lista.find((f) => f.is_principal || f.is_primary) ?? lista[0];
    endereco = escolhida?.url || escolhida?.image_url || "";
  }
  if (!/^https:\/\//.test(endereco)) recusar(`O produto ${produto.name ?? ""} não tem foto. Use outra imagem.`);
  return { corpo: await baixar(endereco), nome: produto.name ?? "produto" };
}

async function baixar(endereco: string): Promise<Buffer> {
  try {
    const resposta = await buscar(endereco, { limite: LIMITE_EM_BYTES, tempoMs: 20_000, aceita: "image/*" });
    if (resposta.status !== 200) recusar(`O link da imagem não abriu (o site respondeu com erro ${resposta.status}). Confira o link.`);
    return resposta.corpo;
  } catch (erro) {
    if (erro instanceof ErroDaFerramenta) throw erro;
    if (erro instanceof ErroDeRede) {
      if (erro.motivo === "grande") recusar("A imagem passa de 15 MB. Mande uma menor.");
      if (erro.motivo === "tempo") recusar("O link da imagem demorou demais para responder. Tente outro link.");
      recusar("Não deu para baixar a imagem desse link. Confira se é o link direto da imagem, começando com https://.");
    }
    throw erro;
  }
}

/** Lê e confere a imagem (envio, link ou produto). `minimo` = menor lado aceito, em pixels. */
export async function receberImagem(ctx: ContextoDoAssistente, valor: unknown, opcoes: { minimo?: number } = {}): Promise<ImagemRecebida> {
  const texto = String(valor ?? "").trim();
  let corpo: Buffer;
  let origem: string;
  /* "envio:<código>", o código sozinho ou o link /enviar/<código> inteiro. */
  const codigoDoEnvio = /^https:\/\//i.test(texto) && !/\/enviar\//.test(texto) ? null : lerCodigoDeEnvio(texto);
  if (codigoDoEnvio) {
    corpo = await bytesDoEnvio(ctx, codigoDoEnvio);
    origem = "imagem enviada por você";
  } else if (/^produto:/i.test(texto)) {
    const produto = await bytesDoProduto(ctx, texto.slice("produto:".length).trim());
    corpo = produto.corpo;
    origem = `foto do produto ${produto.nome}`;
  } else if (/^https:\/\//i.test(texto)) {
    corpo = await baixar(texto);
    origem = texto.length > 80 ? `${texto.slice(0, 77)}...` : texto;
  } else {
    recusar(
      'Imagem não reconhecida. Se a pessoa anexou a imagem na conversa, chame pedir_imagem e mande o link para ela enviar o arquivo; depois use o código "envio:..." que voltar. Também vale um link https:// ou "produto:<id>".'
    );
  }
  if (corpo.length > LIMITE_EM_BYTES) recusar("A imagem passa de 15 MB. Mande uma menor.");

  let meta: sharp.Metadata;
  try {
    meta = await sharp(corpo, { limitInputPixels: 60_000_000, failOn: "error", animated: false }).metadata();
  } catch {
    recusar("O arquivo não é uma imagem que dê para usar. Mande PNG, JPG ou WebP.");
  }
  if (!meta.format || !FORMATOS.has(meta.format)) {
    recusar(meta.format === "svg" ? "Imagem em SVG não é aceita. Mande PNG, JPG ou WebP." : "Este formato de imagem não é aceito. Mande PNG, JPG ou WebP.");
  }
  const largura = meta.width ?? 0;
  const altura = meta.height ?? 0;
  const minimo = opcoes.minimo ?? 48;
  if (largura < minimo || altura < minimo) recusar(`A imagem é pequena demais (${largura} × ${altura}). Mande uma com pelo menos ${minimo} pixels de cada lado.`);
  return { conteudo: corpo, formato: meta.format, largura, altura, origem };
}

/**
 * Grava a imagem pronta no depósito público `products` (o mesmo das telas da
 * loja), com a sessão da pessoa, e devolve o endereço público.
 * `pasta` como as telas usam: "banners/<perfil>", "stories", "catalog/sections"...
 */
export async function guardarImagemDaLoja(
  ctx: ContextoDoAssistente,
  imagem: { conteudo: Buffer; tipo: string; extensao: string },
  pasta: string
): Promise<string> {
  const limpa = pasta.replace(/^\/+|\/+$/g, "");
  if (!/^[a-zA-Z0-9/_-]{1,120}$/.test(limpa) || limpa.includes("..")) throw new Error(`pasta inválida: ${pasta}`);
  const nome = `${limpa}/${Math.random().toString(16).slice(2, 10)}_${Date.now()}.${imagem.extensao}`;
  const { error } = await ctx.supabase.storage.from("products").upload(nome, imagem.conteudo, { contentType: imagem.tipo, upsert: false });
  if (error) {
    console.error("[assistentes] guardar imagem da loja", error.message);
    recusar("Não deu para guardar a imagem agora. Tente de novo em instantes.");
  }
  return ctx.supabase.storage.from("products").getPublicUrl(nome).data.publicUrl;
}
