import { NextResponse } from "next/server";
import { createAdminClient } from "@/lib/supabase-admin";
import {
  chamarAsaas,
  credenciaisAtivas,
  estadoDoPagamento,
  instanteDoPagamento,
  lerConfiguracaoDoAsaas,
  tokensIguais,
} from "@/lib/asaas";
import { reconcileCatalogPaymentWithProvider } from "@/lib/catalog-payment-reconcile";

/*
  O aviso de pagamento do Asaas.

  POR QUE O ENDERECO TEM O ID DA INTEGRACAO DENTRO.

  Um endereco so atende todas as lojas, e cada loja e uma conta Asaas diferente.
  O Asaas se identifica com um cabecalho cujo valor nos escolhemos na hora de
  registrar o webhook — entao cada loja tem um segredo proprio. Para conferir o
  segredo certo, e preciso saber de qual loja o aviso veio ANTES de ler o corpo,
  e o caminho e o unico lugar onde isso cabe.

  Um segredo unico para todo mundo seria mais simples e bem pior: vazou de uma
  loja, vazou de todas.

  IDEMPOTENCIA. O Asaas entrega pelo menos uma vez, e repete quando duvida.
  Nao ha contador de eventos aqui porque nao precisa: a conciliacao relê a
  cobranca no provedor e grava o estado que encontrar. Rodar duas vezes escreve
  duas vezes a mesma coisa.

  E A RESPOSTA E SEMPRE RAPIDA. Quinze falhas seguidas pausam a fila do Asaas, e
  fila pausada e pedido que para de dar baixa sozinho na loja inteira.
*/

export const runtime = "nodejs";

type EventoDoAsaas = {
  id?: unknown;
  event?: unknown;
  payment?: { id?: unknown; externalReference?: unknown } | null;
};

type CobrancaDoAsaas = Record<string, unknown> & {
  status?: unknown;
  confirmedDate?: unknown;
  clientPaymentDate?: unknown;
};

function texto(valor: unknown) {
  return String(valor ?? "").trim();
}

/*
  O Pix do balcao.

  Aqui nao nasce venda nenhuma: quem fecha a venda e o checkout do PDV, quando
  ve a cobranca aprovada. O aviso so encurta a espera — a tela do caixa tambem
  confere sozinha de quatro em quatro segundos.

  Se o Pix cair depois de a tela ter fechado, a cobranca fica aprovada e sem
  pedido, e e exatamente para isso que existe a lista de Pix pendentes no
  balcao: o dinheiro entrou, a venda ainda nao, e alguem precisa ver isso.
*/
async function sincronizarCobrancaDoBalcao(params: {
  admin: ReturnType<typeof createAdminClient>;
  cobranca: { id: string; status: string };
  settings: Record<string, unknown>;
  idDaCobranca: string;
}) {
  const { admin, cobranca, settings, idDaCobranca } = params;
  if (cobranca.status !== "pending") return;

  const credenciais = credenciaisAtivas(lerConfiguracaoDoAsaas(settings));
  if (!credenciais) return;

  const remoto = await chamarAsaas<CobrancaDoAsaas>(`/payments/${encodeURIComponent(idDaCobranca)}`, credenciais, {
    method: "GET",
  });

  const estado = estadoDoPagamento(remoto.status);
  const agora = new Date().toISOString();
  const novoStatus =
    estado === "approved" ? "approved" : estado === "pending" ? "pending" : estado === "rejected" ? "rejected" : "cancelled";

  await admin
    .from("pdv_pix_charges")
    .update({
      status: novoStatus,
      provider_status: texto(remoto.status) || null,
      approved_at:
        novoStatus === "approved"
          ? instanteDoPagamento(remoto.confirmedDate) || instanteDoPagamento(remoto.clientPaymentDate) || agora
          : null,
      cancelled_at: novoStatus === "cancelled" ? agora : null,
      checked_at: agora,
      raw_response: remoto,
      updated_at: agora,
    })
    .eq("id", cobranca.id)
    /* So enquanto pendente: um cancelamento feito no balcao no mesmo instante
       nao pode ser desfeito por um aviso atrasado. */
    .eq("status", "pending");
}

export async function POST(request: Request, { params }: { params: Promise<{ integracao: string }> }) {
  try {
    const { integracao: integrationId } = await params;
    if (!texto(integrationId)) return NextResponse.json({ error: "Aviso sem destino." }, { status: 404 });

    const admin = createAdminClient();
    const { data } = await admin
      .from("integrations")
      .select("id,provider,settings,active")
      .eq("id", integrationId)
      .maybeSingle();

    const linha = (data || null) as { id?: string; provider?: string; settings?: Record<string, unknown> } | null;
    if (!linha?.id || linha.provider !== "asaas") {
      return NextResponse.json({ error: "Aviso sem destino." }, { status: 404 });
    }

    /*
      A conferencia do segredo.

      Vem antes de ler o corpo de propósito: sem isso, qualquer um que
      descobrisse o endereco poderia mandar "pagamento recebido" e levar
      mercadoria sem pagar. E a comparacao percorre a palavra inteira, para o
      tempo de resposta nao entregar o segredo letra por letra.
    */
    const guardado = texto((linha.settings || {}).webhook_token);
    const recebido = texto(request.headers.get("asaas-access-token"));
    if (!tokensIguais(guardado, recebido)) {
      return NextResponse.json({ error: "Aviso nao autenticado." }, { status: 401 });
    }

    const evento = (await request.json().catch(() => ({}))) as EventoDoAsaas;
    const idDaCobranca = texto(evento.payment?.id);
    if (!idDaCobranca) return NextResponse.json({ ok: true, ignored: true });

    /*
      De quem e esta cobranca.

      A busca e por provedor MAIS id da cobranca, nunca so pelo id: os dois
      provedores geram identificadores no formato deles, e um dia dois podem
      coincidir. O indice unico das tres tabelas e exatamente esse par.

      A ordem segue a mesma da rota do Mercado Pago. O link de pagamento ainda
      nao cobra pelo Asaas, entao um aviso dele nao existe para ser ignorado.
    */
    const { data: cobrancaDoBalcao } = await admin
      .from("pdv_pix_charges")
      .select("id,status")
      .eq("provider", "asaas")
      .eq("provider_payment_id", idDaCobranca)
      .maybeSingle();

    const balcao = (cobrancaDoBalcao || null) as { id?: string; status?: string } | null;
    if (balcao?.id) {
      await sincronizarCobrancaDoBalcao({
        admin,
        cobranca: { id: balcao.id, status: String(balcao.status || "") },
        settings: linha.settings || {},
        idDaCobranca,
      });
      return NextResponse.json({ ok: true });
    }

    const { data: pagamento } = await admin
      .from("catalog_order_payments")
      .select("id,user_id,catalog_order_id,amount,integration_id,provider_payment_id,status,updated_at")
      .eq("provider", "asaas")
      .eq("provider_payment_id", idDaCobranca)
      .maybeSingle();

    const local = (pagamento || null) as { id?: string; integration_id?: string } | null;
    if (!local?.id || !local.integration_id) {
      return NextResponse.json({ ok: true, ignored: true });
    }

    /* `forcar` porque aviso do provedor nao espera o intervalo que a consulta
       ativa respeita: se o Asaas avisou, e porque mudou agora. */
    await reconcileCatalogPaymentWithProvider(admin as never, pagamento as never, {
      webhookPayload: evento,
      forcar: true,
    });

    return NextResponse.json({ ok: true });
  } catch {
    /*
      Erro nosso nao pode pausar a fila deles.

      Um 500 aqui conta como falha, e quinze seguidas interrompem os avisos da
      conta inteira. A cobranca continua sendo conferida pela consulta ativa do
      checkout, entao engolir o erro atrasa a confirmacao; devolver erro
      derrubaria o aviso de todos os pedidos daquela loja.
    */
    return NextResponse.json({ ok: true });
  }
}
