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

import { canCompany, getCompanyContext, type CompanyContext } from "@/lib/company-context";
import { assinaturaDaEmpresa } from "@/lib/fiscal/servidor/assinatura-da-empresa";
import { createAdminClient, createClient } from "@/lib/supabase-server";

import { assistentesLiberadosAgora } from "../ajustes";
import { enderecosDoConector } from "./enderecos";
import { origensDoConector, type Enderecos } from "./metadados";
import { conferirPedido, enderecoDeVolta, identidadeDoPrograma, type ParametrosDoPedido, type PedidoDePermissao } from "./pedido";
import { lerDestinoDaVolta, type DestinoDaVolta } from "./redirecionamentos";
import { buscarPrograma, type ProgramaRegistrado } from "./servidor";

/**
 * A conferência que acontece antes de a pessoa ver o botão "Permitir", e de
 * novo quando ela clica nele.
 *
 * São as mesmas perguntas nos dois momentos, na mesma ordem: o programa é
 * conhecido e o retorno é dele; o pedido está bem formado; a pessoa está
 * logada; a conta dela pode ligar assistentes. Fazer a conferência de novo no
 * clique é o que impede alguém de montar o clique por fora da tela.
 */

/* Como a tela descreve o programa e o destino para a pessoa. */
export type ProgramaNaTela = {
  nome: string;
  /* O destino é de um assistente da nossa lista (um site ou um aplicativo dele).
     Programa que volta para o próprio computador escolhe o próprio nome, e a
     tela avisa. */
  conhecido: boolean;
  tipo: DestinoDaVolta["tipo"];
  rotulo: string;
};

export type Avaliacao =
  /* O programa ou o retorno não conferem: o erro aparece na tela e a pessoa não é mandada a lugar nenhum. */
  | { tipo: "erro"; mensagem: string }
  /* O pedido está errado, mas o retorno confere: o erro volta para o programa. */
  | { tipo: "voltar"; destino: string }
  | { tipo: "entrar"; programa: ProgramaNaTela }
  | {
      tipo: "sem_acesso";
      programa: ProgramaNaTela;
      motivo: "nao_liberado" | "sem_permissao" | "assinatura";
      mensagem: string;
      email: string | null;
      /* Para onde a pessoa volta se desistir. */
      voltar: string;
    }
  | {
      tipo: "permitir";
      programa: ProgramaNaTela;
      email: string | null;
      empresa: string | null;
      pediuEscrita: boolean;
      /* O que só o servidor usa, na hora do clique. */
      registro: ProgramaRegistrado;
      pedido: PedidoDePermissao;
      user: User;
      contexto: CompanyContext;
      enderecos: Enderecos;
    };

const PROGRAMA_DESCONHECIDO =
  "O programa que pediu esta conexão não foi reconhecido. Volte ao assistente e comece a conexão de novo. Se o erro continuar, use uma chave de acesso: ela é criada no painel, em Configurações, Integrações, Assistentes e Apps.";

function programaNaTela(registro: ProgramaRegistrado, destino: DestinoDaVolta): ProgramaNaTela {
  return {
    nome: destino.assistente ?? registro.nome,
    conhecido: destino.tipo !== "computador",
    tipo: destino.tipo,
    rotulo: destino.rotulo,
  };
}

export async function avaliarPedido(requisicao: { url: string; headers: Headers }, parametros: ParametrosDoPedido): Promise<Avaliacao> {
  const enderecos = enderecosDoConector(requisicao);
  const admin = createAdminClient();

  const identidade = identidadeDoPrograma(parametros.client_id);
  if (!identidade) return { tipo: "erro", mensagem: PROGRAMA_DESCONHECIDO };
  const registro = await buscarPrograma(admin, identidade, { buscarDocumento: true });
  if (!registro) return { tipo: "erro", mensagem: PROGRAMA_DESCONHECIDO };

  const leitura = conferirPedido(parametros, registro, origensDoConector(enderecos));
  if (!leitura.ok) {
    if ("naTela" in leitura) return { tipo: "erro", mensagem: leitura.naTela };
    return {
      tipo: "voltar",
      destino: enderecoDeVolta(leitura.voltar.redirectUri, {
        error: leitura.voltar.erro,
        error_description: leitura.voltar.descricao,
        state: leitura.voltar.estado,
        iss: enderecos.emissor,
      }),
    };
  }
  const pedido = leitura.pedido;
  const destino = lerDestinoDaVolta(pedido.redirectUri);
  if (!destino) return { tipo: "erro", mensagem: PROGRAMA_DESCONHECIDO };
  const programa = programaNaTela(registro, destino);

  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) return { tipo: "entrar", programa };

  const contexto = await getCompanyContext(supabase, user);
  const semAcesso = (motivo: "nao_liberado" | "sem_permissao" | "assinatura", mensagem: string): Avaliacao => ({
    tipo: "sem_acesso",
    programa,
    motivo,
    mensagem,
    email: user.email ?? null,
    voltar: enderecoDeVolta(pedido.redirectUri, {
      error: "access_denied",
      error_description: "A conta nao pode ligar assistentes.",
      state: pedido.estado,
      iss: enderecos.emissor,
    }),
  });

  if (!(await assistentesLiberadosAgora([contexto.companyId, contexto.ownerId]))) {
    return semAcesso("nao_liberado", "A conexão com assistentes ainda não está liberada para a sua empresa.");
  }
  if (contexto.membership?.active === false || !canCompany(contexto, user.id, "company.assistants.manage")) {
    return semAcesso(
      "sem_permissao",
      "A sua conta não tem permissão para ligar assistentes. Quem libera é o dono da empresa, em Funcionários, na permissão “Usar assistentes e apps”."
    );
  }
  if (contexto.ownerId) {
    const assinatura = await assinaturaDaEmpresa(admin, contexto.ownerId);
    if (!assinatura.ativa && assinatura.situacao !== "sem-registro") {
      return semAcesso("assinatura", "A assinatura do Ktalogu desta empresa está vencida ou inativa. Regularize em Assinatura para ligar um assistente.");
    }
  }

  const { data: perfil } = await supabase
    .from("profiles")
    .select("store_name")
    .eq("id", contexto.ownerId ?? contexto.companyId)
    .maybeSingle();
  const empresa = String((perfil as { store_name?: unknown } | null)?.store_name ?? "").trim() || null;

  return {
    tipo: "permitir",
    programa,
    email: user.email ?? null,
    empresa,
    pediuEscrita: pedido.escoposPedidos.includes("escrita"),
    registro,
    pedido,
    user,
    contexto,
    enderecos,
  };
}

/** Os parâmetros do pedido que a tela guarda e manda de volta no clique. */
export const PARAMETROS_DO_PEDIDO = ["client_id", "redirect_uri", "response_type", "code_challenge", "code_challenge_method", "state", "scope", "resource"] as const;

export function lerParametros(origem: Record<string, unknown>): ParametrosDoPedido {
  const parametros: ParametrosDoPedido = {};
  for (const nome of PARAMETROS_DO_PEDIDO) {
    const valor = origem[nome];
    /* Parâmetro repetido chega como lista: vale o primeiro. */
    const texto = Array.isArray(valor) ? valor[0] : valor;
    if (typeof texto === "string") parametros[nome] = texto;
  }
  return parametros;
}
