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

import { ErroDigital } from "./contexto";

/*
 * "Entregas": os pedidos com produto digital e o que aconteceu com cada um
 * (acesso liberado? e-mail saiu?), o que precisa da loja (pedido sem cliente,
 * produto sem nada para entregar) e os pedidos que esperam pagamento.
 */

export type SituacaoDoEmail = "enviado" | "na_fila" | "falhou" | "sem_email" | "cancelado";

export type EntregaNaLista = {
  pedidoId: string;
  /* O número do pedido no painel (o da tela de pedidos). */
  numero: number | null;
  /* O número que o comprador viu na loja, quando a compra veio de lá. */
  numeroNaLoja: number | null;
  data: string | null;
  cliente: string;
  emailDoCliente: string | null;
  produtos: string[];
  itens: number;
  acesso: "liberado" | "preparando" | "fechado";
  email: { situacao: SituacaoDoEmail; destino: string | null; enviadoEm: string | null; erro: string | null };
  /* O que ainda espera: código, arquivo final (sob encomenda) ou a data da pré-venda. */
  esperando: { codigos: number; arquivosFinais: number; preVendaEm: string | null };
  /* O freio de compartilhamento pausou o acesso (a loja reativa). */
  pausadoPeloFreio: boolean;
};

/* Sob encomenda: cada unidade que espera (ou já recebeu) o arquivo final. */
export type EncomendaNaLista = {
  acessoId: string;
  pedidoId: string;
  numero: number | null;
  numeroNaLoja: number | null;
  cliente: string;
  emailDoCliente: string | null;
  produto: string;
  unidade: number;
  unidades: number;
  entregarAte: string | null;
  atrasada: boolean;
  /* O que o comprador respondeu na compra. */
  respostas: Array<{ pergunta: string; resposta: string }>;
  arquivoFinal: { nome: string; enviadoEm: string } | null;
};

export type Pendencia = { pedidoId: string; numero: number | null; motivo: "sem_cliente" | "sem_entregavel"; data: string };

export type EsperandoPagamento = { pedidoId: string; numero: number | null; data: string; cliente: string; total: number; produtos: string[] };

type LinhaDoAcesso = {
  order_id: string;
  status: string;
  motivo: string | null;
  expira_em: string | null;
  liberado_em: string | null;
  libera_em: string | null;
  products: { name: string | null } | null;
  clients: { name: string | null; email: string | null } | null;
  digital_deliverables: { tipo: string } | null;
};

/* O motivo que o freio grava quando pausa um acesso. */
export const MOTIVO_DO_FREIO = "freio: muitos lugares diferentes";

const ABERTOS = new Set(["ativo", "aguardando_arquivo", "aguardando_codigo", "agendado"]);

/* O número do pedido no painel e o que o comprador viu na loja (quando veio de lá). */
async function numerosDosPedidos(
  admin: SupabaseClient,
  lojaId: string,
  pedidos: string[]
): Promise<Map<string, { numero: number | null; numeroNaLoja: number | null; data: string | null }>> {
  const mapa = new Map<string, { numero: number | null; numeroNaLoja: number | null; data: string | null }>();
  if (!pedidos.length) return mapa;
  const [internos, daLoja] = await Promise.all([
    admin.from("orders").select("id,number,created_at").eq("user_id", lojaId).in("id", pedidos),
    admin.from("catalog_orders").select("approved_order_id,number").eq("user_id", lojaId).in("approved_order_id", pedidos),
  ]);
  for (const linha of (internos.data ?? []) as Array<{ id: string; number: number | null; created_at: string }>) {
    mapa.set(linha.id, { numero: linha.number, numeroNaLoja: null, data: linha.created_at });
  }
  for (const linha of (daLoja.data ?? []) as Array<{ approved_order_id: string; number: number | null }>) {
    const atual = mapa.get(linha.approved_order_id);
    mapa.set(linha.approved_order_id, { numero: atual?.numero ?? null, numeroNaLoja: linha.number, data: atual?.data ?? null });
  }
  return mapa;
}

export async function listarEntregas(
  admin: SupabaseClient,
  lojaId: string
): Promise<{ entregas: EntregaNaLista[]; pendencias: Pendencia[]; esperando: EsperandoPagamento[] }> {
  const { data: acessos, error } = await admin
    .from("digital_entitlements")
    .select("order_id,status,motivo,expira_em,liberado_em,libera_em,products(name),clients(name,email),digital_deliverables(tipo)")
    .eq("user_id", lojaId)
    .eq("origem", "pedido")
    .not("order_id", "is", null)
    .order("criado_em", { ascending: false })
    .limit(3000);
  if (error) throw new ErroDigital("Não foi possível carregar as entregas.", 500);

  const porPedido = new Map<string, LinhaDoAcesso[]>();
  for (const linha of (acessos ?? []) as unknown as LinhaDoAcesso[]) {
    const lista = porPedido.get(linha.order_id) ?? [];
    lista.push(linha);
    porPedido.set(linha.order_id, lista);
  }
  const pedidos = Array.from(porPedido.keys()).slice(0, 300);

  const [numeros, envios, tarefas] = await Promise.all([
    numerosDosPedidos(admin, lojaId, pedidos),
    pedidos.length
      ? admin
          .from("digital_outbox")
          .select("chave_unica,destino,situacao,enviado_em,ultimo_erro")
          .eq("user_id", lojaId)
          .in(
            "chave_unica",
            pedidos.map((id) => `entrega:${id}`)
          )
      : Promise.resolve({ data: [] }),
    admin
      .from("digital_tasks")
      .select("ref,erro,criado_em")
      .eq("user_id", lojaId)
      .eq("tipo", "liberar")
      .eq("situacao", "falhou")
      .in("erro", ["sem_cliente", "sem_entregavel"])
      .order("criado_em", { ascending: false })
      .limit(50),
  ]);
  const envioPorPedido = new Map(
    ((envios.data ?? []) as Array<{ chave_unica: string; destino: string; situacao: string; enviado_em: string | null; ultimo_erro: string | null }>).map(
      (e) => [e.chave_unica.replace(/^entrega:/, ""), e]
    )
  );

  const entregas: EntregaNaLista[] = pedidos.map((pedidoId) => {
    const linhas = porPedido.get(pedidoId) ?? [];
    const primeira = linhas[0];
    const envio = envioPorPedido.get(pedidoId);
    const abertos = linhas.filter((l) => ABERTOS.has(l.status));
    const liberado = abertos.some((l) => l.status === "ativo" && !(l.expira_em && new Date(l.expira_em).getTime() < Date.now()));
    const situacaoDoEmail: SituacaoDoEmail = !envio
      ? "sem_email"
      : envio.situacao === "enviado"
        ? "enviado"
        : envio.situacao === "falhou"
          ? "falhou"
          : envio.situacao === "cancelado"
            ? "cancelado"
            : "na_fila";
    const info = numeros.get(pedidoId);
    const agendados = linhas.filter((l) => l.status === "agendado" && l.libera_em).map((l) => String(l.libera_em)).sort();
    return {
      pedidoId,
      numero: info?.numero ?? null,
      numeroNaLoja: info?.numeroNaLoja ?? null,
      data: info?.data ?? primeira?.liberado_em ?? null,
      cliente: primeira?.clients?.name?.trim() || "Cliente",
      emailDoCliente: primeira?.clients?.email?.trim() || null,
      produtos: Array.from(new Set(linhas.map((l) => l.products?.name?.trim()).filter((n): n is string => Boolean(n)))),
      itens: linhas.length,
      acesso: liberado ? "liberado" : abertos.length ? "preparando" : "fechado",
      email: {
        situacao: situacaoDoEmail,
        destino: envio?.destino ?? null,
        enviadoEm: envio?.enviado_em ?? null,
        erro: situacaoDoEmail === "falhou" || situacaoDoEmail === "cancelado" ? envio?.ultimo_erro ?? null : null,
      },
      esperando: {
        codigos: linhas.filter((l) => l.status === "aguardando_codigo").length,
        arquivosFinais: linhas.filter((l) => l.status === "aguardando_arquivo" && l.digital_deliverables?.tipo === "encomenda").length,
        preVendaEm: agendados[0] ?? null,
      },
      pausadoPeloFreio: linhas.some((l) => l.status === "pausado" && (l.motivo ?? "").startsWith("freio")),
    };
  });

  /* O que precisa da loja. */
  const linhasDeTarefa = ((tarefas.data ?? []) as Array<{ ref: string | null; erro: string; criado_em: string }>).filter(
    (t) => t.ref && /^[0-9a-f-]{36}$/i.test(t.ref) && !porPedido.has(t.ref)
  );
  const numerosDasTarefas = await numerosDosPedidos(admin, lojaId, Array.from(new Set(linhasDeTarefa.map((t) => String(t.ref)))));
  const vistas = new Set<string>();
  const pendencias: Pendencia[] = [];
  for (const tarefa of linhasDeTarefa) {
    const chave = `${tarefa.ref}:${tarefa.erro}`;
    if (vistas.has(chave)) continue;
    vistas.add(chave);
    pendencias.push({
      pedidoId: String(tarefa.ref),
      numero: numerosDasTarefas.get(String(tarefa.ref))?.numero ?? null,
      motivo: tarefa.erro === "sem_cliente" ? "sem_cliente" : "sem_entregavel",
      data: tarefa.criado_em,
    });
  }

  /* Pedidos com digital esperando pagamento (90 dias). */
  const desde = new Date(Date.now() - 90 * 86400000).toISOString();
  const { data: abertosAPagar } = await admin
    .from("orders")
    .select("id,number,created_at,total_amount,clients(name),order_items!inner(title,products!inner(product_kind))")
    .eq("user_id", lojaId)
    .eq("order_items.products.product_kind", "digital")
    .neq("financial_status", "paid")
    .neq("status", "canceled")
    .gt("total_amount", 0)
    .gte("created_at", desde)
    .order("created_at", { ascending: false })
    .limit(50);
  const esperando: EsperandoPagamento[] = ((abertosAPagar ?? []) as unknown as Array<{
    id: string;
    number: number | null;
    created_at: string;
    total_amount: number | null;
    clients: { name: string | null } | null;
    order_items: Array<{ title: string | null }>;
  }>)
    .filter((p) => !porPedido.has(p.id))
    .map((p) => ({
      pedidoId: p.id,
      numero: p.number,
      data: p.created_at,
      cliente: p.clients?.name?.trim() || "Cliente",
      total: Number(p.total_amount) || 0,
      produtos: Array.from(new Set((p.order_items ?? []).map((i) => String(i.title || "").trim()).filter(Boolean))),
    }));

  return { entregas, pendencias, esperando };
}

/* O pedido é desta loja e tem produto digital? (antes de qualquer ação) */
export async function conferirPedidoDigital(admin: SupabaseClient, lojaId: string, pedidoId: string) {
  if (!/^[0-9a-f-]{36}$/i.test(pedidoId)) throw new ErroDigital("Pedido inválido.", 400);
  const { data } = await admin
    .from("orders")
    .select("id,client_id,status,financial_status,clients(name,email,is_system_placeholder)")
    .eq("id", pedidoId)
    .eq("user_id", lojaId)
    .maybeSingle();
  const pedido = data as unknown as {
    id: string;
    client_id: string | null;
    status: string;
    financial_status: string | null;
    clients: { name: string | null; email: string | null; is_system_placeholder: boolean | null } | null;
  } | null;
  if (!pedido) throw new ErroDigital("Pedido não encontrado.", 404);
  const { data: temDigital } = await admin.rpc("pedido_tem_digital", { p_order_id: pedidoId });
  if (!temDigital) throw new ErroDigital("Este pedido não tem produto digital.", 400);
  return pedido;
}

/* "Reenviar": o e-mail da entrega volta para a fila (e pode ir para outro endereço). */
export async function reenviarEntrega(admin: SupabaseClient, params: { lojaId: string; pedidoId: string; email: string | null }) {
  const pedido = await conferirPedidoDigital(admin, params.lojaId, params.pedidoId);
  if (pedido.status === "canceled") throw new ErroDigital("O pedido foi cancelado: não há o que entregar.", 409);

  const { count } = await admin
    .from("digital_entitlements")
    .select("id", { count: "exact", head: true })
    .eq("order_id", params.pedidoId)
    .in("status", Array.from(ABERTOS));
  if (!count) throw new ErroDigital("O acesso deste pedido ainda não foi liberado.", 409);

  const { data: atual } = await admin
    .from("digital_outbox")
    .select("destino")
    .eq("chave_unica", `entrega:${params.pedidoId}`)
    .maybeSingle();
  const { data: compra } = await admin
    .from("catalog_orders")
    .select("id,cart_payload")
    .eq("approved_order_id", params.pedidoId)
    .eq("user_id", params.lojaId)
    .order("created_at", { ascending: false })
    .limit(1);
  const linhaDaCompra = ((compra ?? []) as Array<{ id: string; cart_payload: { email_da_entrega?: string } | null }>)[0];

  const destino = (
    params.email ||
    (atual as { destino?: string } | null)?.destino ||
    linhaDaCompra?.cart_payload?.email_da_entrega ||
    pedido.clients?.email ||
    ""
  )
    .trim()
    .toLowerCase();
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(destino) || destino.length > 320) {
    throw new ErroDigital("Escreva o e-mail para onde a entrega deve ir.", 400);
  }

  const { error } = await admin.from("digital_outbox").upsert(
    {
      user_id: params.lojaId,
      canal: "email",
      tipo: "entrega",
      destino,
      dados: { order_id: params.pedidoId, client_id: pedido.client_id, catalog_order_id: linhaDaCompra?.id ?? null },
      chave_unica: `entrega:${params.pedidoId}`,
      situacao: "pendente",
      tentativas: 0,
      proxima_em: new Date().toISOString(),
      ultimo_erro: null,
      enviado_em: null,
    },
    { onConflict: "chave_unica" }
  );
  if (error) throw new ErroDigital("Não foi possível pôr o e-mail na fila. Tente de novo.", 500);
  return { destino };
}


type LinhaDaEncomenda = {
  id: string;
  order_id: string;
  order_item_id: string | null;
  status: string;
  unidade: number;
  entregar_ate: string | null;
  atualizado_em: string;
  arquivo_final_id: string | null;
  products: { name: string | null } | null;
  clients: { name: string | null; email: string | null } | null;
  digital_deliverables: { tipo: string } | null;
  digital_files: { nome: string } | null;
};

/* O dia de hoje no calendário de Brasília (AAAA-MM-DD). */
function hojeEmBrasilia(): string {
  return new Date().toLocaleDateString("en-CA", { timeZone: "America/Sao_Paulo" });
}

/**
 * Sob encomenda: as unidades esperando o arquivo final (a mais urgente
 * primeiro) e as entregues nos últimos 30 dias (para trocar o arquivo).
 */
export async function listarEncomendas(admin: SupabaseClient, lojaId: string): Promise<EncomendaNaLista[]> {
  const desde = new Date(Date.now() - 30 * 86400000).toISOString();
  const { data, error } = await admin
    .from("digital_entitlements")
    .select(
      "id,order_id,order_item_id,status,unidade,entregar_ate,atualizado_em,arquivo_final_id,products(name),clients(name,email),digital_deliverables!inner(tipo),digital_files!digital_entitlements_arquivo_final_id_fkey(nome)"
    )
    .eq("user_id", lojaId)
    .eq("digital_deliverables.tipo", "encomenda")
    .not("order_id", "is", null)
    .or(`status.eq.aguardando_arquivo,and(status.eq.ativo,atualizado_em.gte.${desde})`)
    .order("entregar_ate", { ascending: true, nullsFirst: false })
    .limit(300);
  if (error) throw new ErroDigital("Não foi possível ler as encomendas.", 500);
  const linhas = (data ?? []) as unknown as LinhaDaEncomenda[];
  if (!linhas.length) return [];

  const pedidos = Array.from(new Set(linhas.map((l) => l.order_id)));
  const itens = Array.from(new Set(linhas.map((l) => l.order_item_id).filter((id): id is string => Boolean(id))));
  const [numeros, respostas] = await Promise.all([
    numerosDosPedidos(admin, lojaId, pedidos),
    itens.length
      ? admin.from("order_items").select("id,quantity,selected_options").in("id", itens.slice(0, 300))
      : Promise.resolve({ data: [] }),
  ]);
  const porItem = new Map(
    ((respostas.data ?? []) as Array<{ id: string; quantity: number | null; selected_options: { custom_fields?: unknown } | null }>).map((item) => [
      item.id,
      item,
    ])
  );
  const hoje = hojeEmBrasilia();
  return linhas
    .map((linha) => {
      const item = linha.order_item_id ? porItem.get(linha.order_item_id) : undefined;
      const campos = Array.isArray(item?.selected_options?.custom_fields) ? (item?.selected_options?.custom_fields as Array<Record<string, unknown>>) : [];
      const info = numeros.get(linha.order_id);
      const pendente = linha.status === "aguardando_arquivo";
      return {
        acessoId: linha.id,
        pedidoId: linha.order_id,
        numero: info?.numero ?? null,
        numeroNaLoja: info?.numeroNaLoja ?? null,
        cliente: linha.clients?.name?.trim() || "Cliente",
        emailDoCliente: linha.clients?.email?.trim() || null,
        produto: linha.products?.name?.trim() || "Produto",
        unidade: linha.unidade,
        unidades: Math.max(1, Math.ceil(Number(item?.quantity) || 1)),
        entregarAte: linha.entregar_ate,
        atrasada: pendente && Boolean(linha.entregar_ate && linha.entregar_ate < hoje),
        respostas: campos
          .map((campo) => ({ pergunta: String(campo.title ?? "").trim(), resposta: String(campo.value ?? "").trim() }))
          .filter((r) => r.pergunta && r.resposta)
          .slice(0, 12),
        arquivoFinal: !pendente && linha.digital_files ? { nome: linha.digital_files.nome, enviadoEm: linha.atualizado_em } : null,
      };
    })
    .sort((a, b) => Number(Boolean(a.arquivoFinal)) - Number(Boolean(b.arquivoFinal)));
}

/** A loja anexa o arquivo final de uma unidade sob encomenda: o comprador recebe sozinho. */
export async function anexarArquivoFinal(
  admin: SupabaseClient,
  params: { lojaId: string; acessoId: string; arquivoId: string; quem: string }
): Promise<void> {
  if (!/^[0-9a-f-]{36}$/i.test(params.acessoId) || !/^[0-9a-f-]{36}$/i.test(params.arquivoId)) {
    throw new ErroDigital("Escolha o arquivo final.", 400);
  }
  const { data: acesso } = await admin
    .from("digital_entitlements")
    .select("id")
    .eq("id", params.acessoId)
    .eq("user_id", params.lojaId)
    .maybeSingle();
  if (!acesso) throw new ErroDigital("Esta encomenda não foi encontrada.", 404);
  const { data, error } = await admin.rpc("entregar_encomenda_digital", {
    p_entitlement_id: params.acessoId,
    p_arquivo_id: params.arquivoId,
    p_quem: params.quem,
  });
  if (error) throw new ErroDigital("Não foi possível entregar agora. Tente de novo.", 500);
  const resultado = (data ?? {}) as { ok?: boolean; motivo?: string };
  if (!resultado.ok) {
    const motivos: Record<string, string> = {
      acesso: "Esta encomenda não foi encontrada.",
      fechado: "O pedido foi cancelado ou o pagamento foi desfeito: não há o que entregar.",
      arquivo: "Esse arquivo não é da sua loja.",
      arquivo_nao_pronto: "Espere o arquivo terminar de enviar e tente de novo.",
    };
    throw new ErroDigital(motivos[String(resultado.motivo)] ?? "Não foi possível entregar.", 409);
  }
}

/**
 * A loja reativa o acesso que o freio de compartilhamento pausou: a conta dos
 * lugares recomeça e os links de e-mail antigos deste pedido deixam de valer
 * (o link que vazou não volta a abrir). O comprador recebe um e-mail novo.
 */
export async function reativarDoFreio(admin: SupabaseClient, params: { lojaId: string; pedidoId: string }): Promise<number> {
  await conferirPedidoDigital(admin, params.lojaId, params.pedidoId);
  const agora = new Date().toISOString();
  const { data, error } = await admin
    .from("digital_entitlements")
    .update({ status: "ativo", motivo: null, freio_liberado_em: agora, atualizado_em: agora })
    .eq("user_id", params.lojaId)
    .eq("order_id", params.pedidoId)
    .eq("status", "pausado")
    .like("motivo", "freio%")
    .select("id");
  if (error) throw new ErroDigital("Não foi possível reativar agora.", 500);
  const reativados = (data ?? []).length;
  if (reativados) {
    await admin
      .from("digital_access_keys")
      .update({ expira_em: agora })
      .eq("user_id", params.lojaId)
      .eq("order_id", params.pedidoId)
      .eq("finalidade", "entrega")
      .gt("expira_em", agora);
  }
  return reativados;
}
