/**
 * A fila das fotos importadas.
 *
 * Os produtos nascem na hora; as fotos chegam depois, porque baixar dezenas
 * de fotos de outro site leva minutos e o assistente não espera tanto. A fila
 * mora no banco (`importacao_fotos`), então um deploy no meio não perde nada:
 * a fila volta a andar na próxima importação, consulta ou abertura do painel.
 *
 * Cada foto é baixada pela mesma porta protegida da leitura (nunca endereço
 * de dentro), conferida (tem de ser foto de verdade, de um tamanho razoável),
 * reduzida para no máximo 1600 px no formato WebP (sem os dados escondidos da
 * câmera) e guardada no mesmo lugar das fotos do painel. A linha dela em
 * `product_images` usa o id que a opção de cor já espera.
 *
 * Duas de cada vez: o servidor tem dois núcleos e atende o painel de todo
 * mundo ao mesmo tempo.
 */

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

import { createAdminClient } from "@/lib/supabase-server";

import { buscar, ErroDeRede } from "./rede";

const DE_CADA_VEZ = 2;
const TENTATIVAS = 3;
const LIMITE_DA_FOTO = 15 * 1024 * 1024;
const LADO_MAXIMO = 1600;
const PIXELS_MAXIMOS = 40_000_000;
const PRESA_HA_MS = 5 * 60 * 1000;
const FORMATOS = new Set(["jpeg", "png", "webp", "gif", "avif", "tiff", "heif"]);

type Foto = {
  id: string;
  dono_id: string;
  product_id: string;
  endereco: string;
  posicao: number;
  tentativas: number;
  situacao: string;
};

class FalhaDaFoto extends Error {
  readonly tentarDeNovo: boolean;
  constructor(mensagem: string, tentarDeNovo: boolean) {
    super(mensagem);
    this.tentarDeNovo = tentarDeNovo;
  }
}

declare global {
  var __filaDasFotosImportadas: { rodando: boolean; deNovo: boolean } | undefined;
}

/** Põe a fila para andar, se ela estiver parada. Volta na hora. */
export function acordarFilaDeFotos(): void {
  const estado = (globalThis.__filaDasFotosImportadas ??= { rodando: false, deNovo: false });
  if (estado.rodando) {
    estado.deNovo = true;
    return;
  }
  estado.rodando = true;
  void (async () => {
    try {
      do {
        estado.deNovo = false;
        await rodar();
      } while (estado.deNovo);
    } catch (erro) {
      console.error("[importacao] a fila de fotos parou:", erro instanceof Error ? erro.message : erro);
    } finally {
      estado.rodando = false;
    }
  })();
}

async function rodar() {
  const admin = createAdminClient();
  for (;;) {
    const fotos = await pegarProximas(admin, DE_CADA_VEZ * 2);
    if (!fotos.length) return;
    let proxima = 0;
    await Promise.all(
      Array.from({ length: Math.min(DE_CADA_VEZ, fotos.length) }, async () => {
        while (proxima < fotos.length) {
          const foto = fotos[proxima];
          proxima += 1;
          await cuidar(admin, foto);
        }
      })
    );
  }
}

/** As próximas da fila, já marcadas como "baixando" (quem marca primeiro leva). */
async function pegarProximas(admin: SupabaseClient, quantas: number): Promise<Foto[]> {
  const presaDesde = new Date(Date.now() - PRESA_HA_MS).toISOString();
  const { data, error } = await admin
    .from("importacao_fotos")
    .select("id,dono_id,product_id,endereco,posicao,tentativas,situacao")
    .or(`situacao.eq.pendente,and(situacao.eq.baixando,atualizada_em.lt.${presaDesde})`)
    .order("atualizada_em", { ascending: true })
    .limit(quantas);
  if (error) throw error;

  const minhas: Foto[] = [];
  for (const foto of (data ?? []) as Foto[]) {
    if (foto.tentativas >= TENTATIVAS) {
      await marcar(admin, foto.id, "falhou", "A foto não chegou depois de várias tentativas.");
      continue;
    }
    const { data: pega } = await admin
      .from("importacao_fotos")
      .update({ situacao: "baixando", tentativas: foto.tentativas + 1, atualizada_em: new Date().toISOString() })
      .eq("id", foto.id)
      .eq("situacao", foto.situacao)
      .eq("tentativas", foto.tentativas)
      .select("id")
      .maybeSingle();
    if (pega) minhas.push({ ...foto, tentativas: foto.tentativas + 1 });
  }
  return minhas;
}

async function marcar(admin: SupabaseClient, id: string, situacao: "pendente" | "pronta" | "falhou", erro: string | null) {
  await admin
    .from("importacao_fotos")
    .update({ situacao, erro: erro ? erro.slice(0, 300) : null, atualizada_em: new Date().toISOString() })
    .eq("id", id);
}

async function cuidar(admin: SupabaseClient, foto: Foto) {
  try {
    await trazer(admin, foto);
    await marcar(admin, foto.id, "pronta", null);
  } catch (erro) {
    const falha =
      erro instanceof FalhaDaFoto
        ? erro
        : erro instanceof ErroDeRede
          ? new FalhaDaFoto(erro.message, erro.motivo === "tempo" || erro.motivo === "conexao")
          : new FalhaDaFoto("Não foi possível preparar esta foto.", true);
    if (!(erro instanceof FalhaDaFoto) && !(erro instanceof ErroDeRede)) {
      console.error("[importacao] falha na foto:", foto.id, erro instanceof Error ? erro.message : erro);
    }
    const deNovo = falha.tentarDeNovo && foto.tentativas < TENTATIVAS;
    await marcar(admin, foto.id, deNovo ? "pendente" : "falhou", falha.message);
  }
}

/** Baixa, confere, reduz, guarda e liga a foto ao produto. */
async function trazer(admin: SupabaseClient, foto: Foto) {
  const resposta = await buscar(foto.endereco, {
    limite: LIMITE_DA_FOTO,
    tempoMs: 30_000,
    aceita: "image/avif,image/webp,image/png,image/jpeg,image/*;q=0.8,*/*;q=0.5",
  });
  if (resposta.status !== 200) {
    const deNovo = resposta.status >= 500 || resposta.status === 429 || resposta.status === 408;
    throw new FalhaDaFoto(resposta.status === 404 ? "A foto não existe mais na loja antiga (erro 404)." : `A loja antiga recusou a foto (erro ${resposta.status}).`, deNovo);
  }

  let pronta: Buffer;
  try {
    const imagem = sharp(resposta.corpo, { limitInputPixels: PIXELS_MAXIMOS, failOn: "error", animated: false });
    const meta = await imagem.metadata();
    if (!meta.format || !FORMATOS.has(meta.format)) throw new FalhaDaFoto("O arquivo não é uma foto (JPEG, PNG, WebP, GIF ou AVIF).", false);
    if ((meta.width ?? 0) < 50 || (meta.height ?? 0) < 50) throw new FalhaDaFoto("A foto é pequena demais (menos de 50 pixels).", false);
    pronta = await imagem
      .rotate()
      .resize({ width: LADO_MAXIMO, height: LADO_MAXIMO, fit: "inside", withoutEnlargement: true })
      .webp({ quality: 82 })
      .toBuffer();
  } catch (erro) {
    if (erro instanceof FalhaDaFoto) throw erro;
    throw new FalhaDaFoto("O arquivo não é uma foto que dê para abrir.", false);
  }

  const caminho = `${foto.dono_id}/products/${foto.product_id}/importadas/${foto.id}.webp`;
  const { error: erroAoGuardar } = await admin.storage.from("products").upload(caminho, pronta, {
    contentType: "image/webp",
    cacheControl: "31536000",
    upsert: true,
  });
  if (erroAoGuardar) throw new FalhaDaFoto("Não foi possível guardar a foto agora.", true);
  const endereco = admin.storage.from("products").getPublicUrl(caminho).data.publicUrl;

  const { error: erroDaLinha } = await admin.from("product_images").insert({
    id: foto.id,
    user_id: foto.dono_id,
    product_id: foto.product_id,
    image_url: endereco,
    sort_order: foto.posicao,
    is_primary: false,
  });
  if (erroDaLinha && erroDaLinha.code !== "23505") {
    /* Produto apagado no meio do caminho: a foto guardada sai junto. */
    await admin.storage.from("products").remove([caminho]);
    throw new FalhaDaFoto(erroDaLinha.code === "23503" ? "O produto foi apagado antes de a foto chegar." : "Não foi possível ligar a foto ao produto.", erroDaLinha.code !== "23503");
  }
  await arrumarPrincipal(admin, foto.product_id);
}

/** A principal é a de menor posição que já chegou: ela vira a foto do produto. */
async function arrumarPrincipal(admin: SupabaseClient, produtoId: string) {
  const { data } = await admin
    .from("product_images")
    .select("id,image_url,is_primary")
    .eq("product_id", produtoId)
    .order("sort_order", { ascending: true })
    .order("created_at", { ascending: true });
  const fotos = (data ?? []) as Array<{ id: string; image_url: string; is_primary: boolean }>;
  if (!fotos.length) return;
  const principal = fotos[0];
  if (!principal.is_primary) {
    await admin.from("product_images").update({ is_primary: false }).eq("product_id", produtoId).neq("id", principal.id);
    await admin.from("product_images").update({ is_primary: true }).eq("id", principal.id);
  }
  await admin.from("products").update({ image_url: principal.image_url }).eq("id", produtoId);
}
