/* Extensão explícita e caminho relativo: é o que o runner de teste do Node
   exige para resolver o módulo, e o `@/` não existe fora do bundler do Next.
   Quem decide para qual conta o dinheiro vai precisa ser testável. */
import { getActiveMercadoPagoCredentials, parseMercadoPagoSettings } from "./mercado-pago.ts";
import { credenciaisAtivas as credenciaisDoAsaas, lerConfiguracaoDoAsaas } from "./asaas.ts";

/*
  QUEM COBRA, ONDE E POR QUAL MEIO.

  A primeira versão disto respondia "quem cobra?" olhando só qual provedor
  estava ligado, e para garantir uma resposta só, conectar o Asaas DESLIGAVA o
  Mercado Pago. Era uma escolha binária imposta a quem quer as duas coisas:
  receber o Pix da loja no Asaas e manter cartão e boleto no Mercado Pago.

  Agora a pergunta é por LOCAL e por MEIO, e a resposta sai de três camadas,
  nesta ordem:

    1. O provedor sabe fazer aquele meio naquele local? Quem não sabe, não
       entra. É isso que impede o cartão de sumir da loja sem aviso quando o
       lojista manda o Pix para o Asaas.
    2. Existe escolha gravada na engrenagem para aquele par? Se existe e o
       provedor escolhido continua servindo, é ela que vale.
    3. Senão, o automático de sempre: o que foi salvo por último.

  A camada 3 é o que faz ninguém quebrar. Quem nunca abriu a engrenagem não tem
  escolha gravada e continua exatamente como estava. E quem gravou uma escolha
  para um provedor que depois foi desconectado cai de volta no automático, em
  vez de a loja parar de vender.
*/

export type NomeDoProvedor = "mercado_pago" | "asaas";

/* Os quatro lugares do sistema que cobram de alguém. */
export type Local = "loja" | "balcao" | "link" | "assinatura";

export type Meio = "pix" | "cartao" | "boleto";

export const LOCAIS: Local[] = ["loja", "balcao", "link", "assinatura"];
export const MEIOS: Meio[] = ["pix", "cartao", "boleto"];

export type LinhaDaIntegracao = {
  id: string;
  user_id: string;
  provider: string;
  token?: string | null;
  refresh_token?: string | null;
  settings: Record<string, unknown> | null;
  active: boolean | null;
  updated_at?: string | null;
};

export type Rota = { surface: string; method: string; provider: string };

export type ProvedorEscolhido = {
  provedor: NomeDoProvedor;
  integracao: LinhaDaIntegracao;
};

export type MeioResolvido = ProvedorEscolhido & { meio: Meio };

/* O estado completo de pagamento de uma conta: as integrações e as escolhas. */
export type EstadoDePagamento = {
  integracoes: LinhaDaIntegracao[];
  rotas: Rota[];
};

/*
  O QUE CADA PROVEDOR SABE FAZER, EM CADA LUGAR.

  Esta tabela é a fonte da verdade da tela e do checkout ao mesmo tempo. Se ela
  disser que o Asaas não faz cartão na loja, o botão de cartão não aparece pelo
  Asaas — nem na engrenagem, nem para o cliente.

  Por que cada ausência existe:

  - Asaas e cartão: o Asaas não tem tokenização no navegador como o Bricks do
    Mercado Pago. O número do cartão teria que passar pelo nosso servidor, o que
    nos joga em escopo PCI pesado. Fica fora até existir fatura hospedada.
  - Asaas no link e na assinatura: ainda não construído.
  - Cartão e boleto no balcão: cartão no balcão é a maquininha física, que não
    passa pelo sistema; e ninguém emite boleto para quem está na sua frente.
  - Pix e boleto na assinatura: cobrança recorrente automática só existe no
    cartão, porque é o único meio que se cobra sem o cliente agir de novo.
*/
export const SABE_FAZER: Record<NomeDoProvedor, Record<Local, Meio[]>> = {
  mercado_pago: {
    loja: ["pix", "cartao", "boleto"],
    balcao: ["pix"],
    link: ["pix", "cartao", "boleto"],
    assinatura: ["cartao"],
  },
  asaas: {
    /* Cartão entra aqui por FATURA HOSPEDADA: o comprador digita o cartão numa
       página do próprio Asaas e volta. Não existe cartão transparente pelo
       Asaas, e é de propósito — ver o cabeçalho de `asaas.ts`. */
    loja: ["pix", "cartao", "boleto"],
    /* No balcão só Pix: cartão presencial é a maquininha, e boleto no caixa
       não é pagamento, é promessa. */
    balcao: ["pix"],
    link: [],
    assinatura: [],
  },
};

export function ehProvedorConhecido(valor: unknown): valor is NomeDoProvedor {
  return valor === "mercado_pago" || valor === "asaas";
}

export function provedorSabe(provedor: NomeDoProvedor, local: Local, meio: Meio) {
  return SABE_FAZER[provedor][local].includes(meio);
}

/* Todos os meios que algum provedor faz naquele local, na ordem em que a tela
   deve mostrá-los. */
export function meiosPossiveis(local: Local): Meio[] {
  return MEIOS.filter((meio) => (Object.keys(SABE_FAZER) as NomeDoProvedor[]).some((p) => provedorSabe(p, local, meio)));
}

/* Os nomes em português, num lugar só. A tela, a rota e a mensagem de erro
   escrevem a mesma coisa porque leem daqui. */
export const ROTULO_DO_LOCAL: Record<Local, string> = {
  loja: "Loja virtual",
  balcao: "Balcão do PDV",
  link: "Link de pagamento",
  assinatura: "Assinatura recorrente",
};

export const EXPLICACAO_DO_LOCAL: Record<Local, string> = {
  loja: "O que o cliente paga no checkout do seu catálogo.",
  balcao: "O QR que aparece na tela virada para o cliente, no caixa.",
  link: "Cobranças avulsas que você manda por link.",
  assinatura: "Cobrança que se repete sozinha todo mês.",
};

export const ROTULO_DO_MEIO: Record<Meio, string> = {
  pix: "Pix",
  cartao: "Cartão de crédito",
  boleto: "Boleto",
};

export const ROTULO_DO_PROVEDOR: Record<NomeDoProvedor, string> = {
  mercado_pago: "Mercado Pago",
  asaas: "Asaas",
};

/* Por que um provedor não aparece como opção em certo lugar. A tela escreve
   isso ao lado da linha, para o lojista não ficar procurando um menu que não
   existe. */
export function porQueNaoTem(provedor: NomeDoProvedor, local: Local, meio: Meio): string | null {
  if (provedorSabe(provedor, local, meio)) return null;
  if (provedor === "asaas" && meio === "cartao") {
    return "O Asaas não tem tokenização no navegador, então o número do cartão passaria pelo nosso servidor.";
  }
  if (provedor === "asaas" && (local === "link" || local === "assinatura")) {
    return "Ainda não construído para o Asaas.";
  }
  if (provedor === "asaas" && meio === "boleto") return "Ainda não construído para o Asaas.";
  return null;
}

/*
  "Checkout do catálogo ligado" é escolha da VITRINE.

  Quem vende só presencial mantém a loja online fechada e mesmo assim precisa do
  QR no caixa. Amarrar isso ao local, e não a um parâmetro que se esquece de
  passar, torna o engano impossível.
*/
function exigeCheckoutAberto(local: Local) {
  return local !== "balcao";
}

/*
  SANDBOX NÃO COBRA CLIENTE DE VERDADE.

  Uma integração em ambiente de testes gera cobrança que ninguém paga: o QR é
  falso e o dinheiro nunca entra. Deixá-la ativa numa loja no ar é o pior erro
  possível desta integração, porque ele não dá erro em lugar nenhum — o cliente
  vê um código bonito, tenta pagar, e o pedido fica pendente para sempre.

  O critério é o ambiente do SERVIDOR, e não uma escolha de tela: em produção,
  sandbox está fora; na máquina de quem desenvolve, ele é o único jeito de
  testar sem mover dinheiro. Assim a mesma configuração é segura nos dois
  lugares, sem ninguém precisar lembrar de trocar nada antes de subir.
*/
function ambienteDaIntegracaoServe(ambiente: "sandbox" | "production") {
  if (ambiente === "production") return true;
  return process.env.NODE_ENV !== "production";
}

/* Serve para cobrar neste local? Chave completa nos dois provedores, checkout
   aberto onde a vitrine manda, e nada de sandbox em produção. */
export function integracaoServeParaCobrar(linha: LinhaDaIntegracao | null | undefined, local: Local) {
  if (!linha?.id || linha.active === false) return false;
  const exigirCheckout = exigeCheckoutAberto(local);

  if (linha.provider === "asaas") {
    const config = lerConfiguracaoDoAsaas(linha.settings);
    if (!credenciaisDoAsaas(config)) return false;
    if (!ambienteDaIntegracaoServe(config.environment)) return false;
    if (exigirCheckout && !config.checkout_enabled) return false;
    /* Com os três meios desligados, a integração está conectada e não cobra
       nada. Qual deles vale em cada caso é `integracaoAceitaOMeio`. */
    return config.aceita_pix || config.aceita_boleto || config.aceita_cartao;
  }

  if (linha.provider === "mercado_pago") {
    const config = parseMercadoPagoSettings(linha.settings);
    if (!getActiveMercadoPagoCredentials(config)) return false;
    if (!ambienteDaIntegracaoServe(config.environment)) return false;
    return !exigirCheckout || config.checkout_enabled;
  }

  return false;
}

/*
  Os meios que ESTA loja ligou neste provedor.

  `SABE_FAZER` responde o que o provedor consegue fazer; isto responde o que o
  lojista quer que ele faça. Os dois precisam concordar para um meio aparecer
  no checkout, e são coisas diferentes de propósito: o Asaas sabe boleto em
  qualquer loja, mas só cobra boleto em quem marcou a caixa.
*/
export function integracaoAceitaOMeio(linha: LinhaDaIntegracao | null | undefined, meio: Meio) {
  if (!linha) return false;

  if (linha.provider === "asaas") {
    const config = lerConfiguracaoDoAsaas(linha.settings);
    if (meio === "pix") return config.aceita_pix;
    if (meio === "boleto") return config.aceita_boleto;
    return config.aceita_cartao;
  }

  /* O Mercado Pago não tem interruptor por meio: o que ele sabe fazer, ele
     faz. Ligar um por um lá seria inventar uma configuração que nunca existiu
     e que ninguém pediu. */
  return linha.provider === "mercado_pago";
}

/* Quem foi salvo por último. Sem data, vai para o fim da fila: uma linha sem
   `updated_at` não pode ganhar de uma com data conhecida. */
function salvoEm(linha: LinhaDaIntegracao) {
  const instante = linha.updated_at ? new Date(linha.updated_at).getTime() : 0;
  return Number.isFinite(instante) ? instante : 0;
}

/* As integrações que podem cobrar este meio, neste local. */
function candidatas(estado: EstadoDePagamento, local: Local, meio: Meio) {
  return (estado.integracoes || []).filter(
    (linha) =>
      ehProvedorConhecido(linha.provider) &&
      integracaoServeParaCobrar(linha, local) &&
      provedorSabe(linha.provider, local, meio) &&
      integracaoAceitaOMeio(linha, meio)
  );
}

/*
  O provedor de um par local + meio.

  Devolve `null` quando nenhum provedor disponível faz aquele meio ali. Quem
  desenha a tela usa isso para não oferecer um caminho que daria erro.
*/
export function provedorDoMeio(estado: EstadoDePagamento, local: Local, meio: Meio): ProvedorEscolhido | null {
  const possiveis = candidatas(estado, local, meio);
  if (possiveis.length === 0) return null;

  const escolhida = (estado.rotas || []).find((r) => r.surface === local && r.method === meio);
  if (escolhida) {
    const preferida = possiveis.find((linha) => linha.provider === escolhida.provider);
    /* Escolha gravada para provedor que saiu do ar cai no automático, em vez de
       deixar a loja sem cobrar. */
    if (preferida) return { provedor: preferida.provider as NomeDoProvedor, integracao: preferida };
  }

  const vencedora = possiveis.reduce((melhor, atual) => (salvoEm(atual) > salvoEm(melhor) ? atual : melhor));
  return { provedor: vencedora.provider as NomeDoProvedor, integracao: vencedora };
}

/* Tudo que este local consegue cobrar agora, já com quem cobra cada coisa. É o
   que a vitrine usa para desenhar os botões de pagamento. */
export function meiosDoLocal(estado: EstadoDePagamento, local: Local): MeioResolvido[] {
  return meiosPossiveis(local)
    .map((meio) => {
      const escolhido = provedorDoMeio(estado, local, meio);
      return escolhido ? { meio, ...escolhido } : null;
    })
    .filter((item): item is MeioResolvido => item !== null);
}

type AdminLike = {
  from: (table: string) => {
    select: (columns: string) => {
      in: (column: string, values: string[]) => {
        eq: (column: string, value: string) => Promise<{ data: unknown; error: unknown }>;
      };
      eq: (column: string, value: string) => Promise<{ data: unknown; error: unknown }>;
    };
  };
};

/*
  Lê integrações e escolhas numa vez só.

  As duas consultas saem juntas porque toda decisão precisa das duas: separar
  daria duas idas ao banco por pergunta, e o banco mora longe.
*/
export async function carregarPagamento(admin: AdminLike, ownerId: string): Promise<EstadoDePagamento> {
  const [integracoes, rotas] = await Promise.all([
    admin
      .from("integrations")
      .select("id,user_id,provider,token,refresh_token,settings,active,updated_at")
      .in("provider", ["mercado_pago", "asaas"])
      .eq("user_id", ownerId),
    admin.from("payment_routing").select("surface,method,provider").eq("user_id", ownerId),
  ]);

  if (integracoes.error) {
    const mensagem = (integracoes.error as { message?: string })?.message;
    throw new Error(mensagem || "Não foi possível carregar a integração de pagamento.");
  }

  /* A engrenagem é opcional: falha ao ler as escolhas não pode derrubar a
     cobrança, e sem elas o automático já responde. */
  return {
    integracoes: (integracoes.data || []) as LinhaDaIntegracao[],
    rotas: rotas.error ? [] : ((rotas.data || []) as Rota[]),
  };
}

/*
  O atalho das rotas: quem cobra neste local.

  Sem `meio`, devolve o primeiro meio disponível — serve para a pergunta "esta
  loja cobra online?". Com `meio`, devolve exatamente quem cobra aquilo.
*/
export async function provedorDoLocal(
  admin: AdminLike,
  ownerId: string,
  local: Local,
  meio?: Meio
): Promise<ProvedorEscolhido | null> {
  const estado = await carregarPagamento(admin, ownerId);
  if (meio) return provedorDoMeio(estado, local, meio);
  return meiosDoLocal(estado, local)[0] ?? null;
}
