import type { SupabaseClient, User } from "@supabase/supabase-js";
import { NextResponse } from "next/server";

import { canCompany, getCompanyContext, getEffectiveCompanyRole, type CompanyContext } from "@/lib/company-context";
import { createAdminClient, createClient } from "@/lib/supabase-server";

import { gerarChave, lerEscopos, MAXIMO_DE_CHAVES_ATIVAS, MAXIMO_DE_CHAVES_POR_DIA, situacaoDaChave, type Escopo, type SituacaoDaChave } from "./chave";
import { assistentesLiberadosAgora } from "./ajustes";
import { enderecosDoConector } from "./oauth/enderecos";
import { segredoDoServidor } from "./segredo";

/**
 * O lado do painel: quem pode mexer nas chaves, e as chaves em si.
 *
 * As tabelas das chaves são fechadas para o navegador. Tudo passa por estas
 * funções, que conferem a pessoa logada e a empresa dela antes de tocar no
 * banco com a chave de serviço, sempre filtrando pela empresa.
 */

export type AcessoDoPainel =
  | {
      ok: true;
      user: User;
      empresa: CompanyContext;
      admin: SupabaseClient;
      /* Dono e admin enxergam e revogam as chaves de todo mundo da empresa. */
      verTodas: boolean;
    }
  | { ok: false; motivo: "nao_autenticado" | "nao_liberado" | "sem_permissao" };

export async function acessoDoPainel(): Promise<AcessoDoPainel> {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) return { ok: false, motivo: "nao_autenticado" };

  const empresa = await getCompanyContext(supabase, user);
  if (!(await assistentesLiberadosAgora([empresa.companyId, empresa.ownerId]))) return { ok: false, motivo: "nao_liberado" };
  if (!canCompany(empresa, user.id, "company.assistants.manage")) return { ok: false, motivo: "sem_permissao" };

  const papel = getEffectiveCompanyRole(empresa, user.id);
  return { ok: true, user, empresa, admin: createAdminClient(), verTodas: papel === "owner" || papel === "admin" };
}

/** A resposta de recusa das rotas que gravam. */
export function recusaDoPainel(motivo: Extract<AcessoDoPainel, { ok: false }>["motivo"]) {
  if (motivo === "nao_autenticado") return NextResponse.json({ error: "Não autenticado." }, { status: 401 });
  if (motivo === "nao_liberado") return NextResponse.json({ error: "Assistentes e apps ainda não estão liberados para esta empresa." }, { status: 403 });
  return NextResponse.json({ error: "Sem permissão para usar assistentes e apps." }, { status: 403 });
}

/**
 * O pedido que grava veio da própria tela do painel?
 *
 * O navegador manda a origem em todo pedido que grava, e só manda o cookie da
 * sessão para o próprio site. Uma página de outro endereço (até de outro
 * subdomínio da Ktalogu) não passa daqui: criar chave, revogar e desfazer só
 * respondem à tela.
 */
export function pedidoDaPropriaTela(request: Request): boolean {
  const deOnde = request.headers.get("sec-fetch-site");
  if (deOnde && deOnde !== "same-origin") return false;
  const origem = request.headers.get("origin");
  if (!origem) return false;
  const enderecos = enderecosDoConector(request);
  return [enderecos.painel, enderecos.pedida].some((endereco) => {
    try {
      return new URL(endereco).origin === origem;
    } catch {
      return false;
    }
  });
}

export const RECUSA_DE_ORIGEM = () => NextResponse.json({ error: "Pedido recusado." }, { status: 403 });

/**
 * Desliga todas as chaves e conexões de uma pessoa numa empresa. É o que
 * acontece quando ela é desativada ou perde a permissão de usar assistentes:
 * se a permissão voltar um dia, o acesso antigo não volta junto sem ninguém
 * ver, e ela liga de novo.
 */
export async function revogarAcessosDosAssistentes(admin: SupabaseClient, quem: { companyId: string; usuarioId: string; por: string }) {
  const { error } = await admin
    .from("chaves_de_acesso")
    .update({ revogada_em: new Date().toISOString(), revogada_por: quem.por })
    .eq("company_id", quem.companyId)
    .eq("usuario_id", quem.usuarioId)
    .is("revogada_em", null);
  if (error) console.error("[assistentes] não foi possível desligar os acessos de quem perdeu a permissão:", error.message);
}

/**
 * O endereço que a pessoa cola no assistente.
 *
 * Em produção é o subdomínio do conector. Fora dela (na máquina de quem
 * desenvolve) é a própria rota, no endereço do painel.
 */
export function enderecoDoMcp(request: Request) {
  return enderecosDoConector(request).conector;
}

export type ChaveNaLista = {
  id: string;
  /* "chave": criada e colada à mão. "conexao": nasceu do botão Conectar de um assistente. */
  origem: "chave" | "conexao";
  nome: string;
  prefixo: string;
  escopos: Escopo[];
  situacao: SituacaoDaChave;
  criadaEm: string;
  expiraEm: string | null;
  ultimoUsoEm: string | null;
  ultimoCliente: string | null;
  de: string | null;
  minha: boolean;
};

type LinhaDaChave = {
  id: string;
  usuario_id: string;
  usuario_email: string | null;
  nome: string;
  prefixo: string;
  escopos: string[] | null;
  expira_em: string | null;
  ultimo_uso_em: string | null;
  ultimo_cliente: string | null;
  revogada_em: string | null;
  criada_em: string;
  origem: string | null;
  renovacao_expira_em: string | null;
};

const COLUNAS = "id,usuario_id,usuario_email,nome,prefixo,escopos,expira_em,ultimo_uso_em,ultimo_cliente,revogada_em,criada_em,origem,renovacao_expira_em";

/* A conexão pelo botão não tem validade escolhida: ela se desfaz quando fica
   parada demais, e é essa data que conta como o fim dela. */
const fimDe = (linha: LinhaDaChave) => (linha.origem === "conexao" ? linha.renovacao_expira_em : linha.expira_em);

function paraALista(linha: LinhaDaChave, usuarioId: string): ChaveNaLista {
  const origem = linha.origem === "conexao" ? "conexao" : "chave";
  return {
    id: linha.id,
    origem,
    nome: linha.nome,
    prefixo: linha.prefixo,
    escopos: lerEscopos(linha.escopos) ?? ["leitura"],
    situacao: situacaoDaChave({ revogada_em: linha.revogada_em, expira_em: fimDe(linha) }),
    criadaEm: linha.criada_em,
    expiraEm: origem === "conexao" ? null : linha.expira_em,
    ultimoUsoEm: linha.ultimo_uso_em,
    ultimoCliente: linha.ultimo_cliente,
    de: linha.usuario_email,
    minha: linha.usuario_id === usuarioId,
  };
}

/* Chave revogada ou vencida há mais de 30 dias sai da lista: já contou a
   história dela. O registro continua no banco, para a atividade. */
const HISTORIA_POR_MS = 30 * 24 * 60 * 60 * 1000;

/**
 * As chaves que a pessoa pode ver: as dela, ou todas da empresa para dono e admin.
 *
 * São duas leituras. As chaves que ainda valem vêm todas, sempre: uma chave
 * que funciona e não aparece na lista é uma chave que ninguém consegue
 * revogar. As que já não valem (revogadas ou vencidas) vêm só as recentes, e
 * com teto, porque essa parte pode crescer à vontade.
 */
export async function listarChaves(acesso: Extract<AcessoDoPainel, { ok: true }>): Promise<ChaveNaLista[]> {
  const agora = new Date().toISOString();
  const corte = new Date(Date.now() - HISTORIA_POR_MS).toISOString();

  let validas = acesso.admin
    .from("chaves_de_acesso")
    .select(COLUNAS)
    .eq("company_id", acesso.empresa.companyId)
    .is("revogada_em", null)
    .or(`expira_em.is.null,expira_em.gt.${agora}`)
    .order("criada_em", { ascending: false })
    .limit(1000);
  let encerradas = acesso.admin
    .from("chaves_de_acesso")
    .select(COLUNAS)
    .eq("company_id", acesso.empresa.companyId)
    .or(`revogada_em.gt.${corte},and(revogada_em.is.null,expira_em.lte.${agora},expira_em.gt.${corte})`)
    .order("criada_em", { ascending: false })
    .limit(100);
  if (!acesso.verTodas) {
    validas = validas.eq("usuario_id", acesso.user.id);
    encerradas = encerradas.eq("usuario_id", acesso.user.id);
  }

  const [asValidas, asEncerradas] = await Promise.all([validas, encerradas]);
  if (asValidas.error) throw new Error(asValidas.error.message);
  if (asEncerradas.error) throw new Error(asEncerradas.error.message);

  const porId = new Map<string, LinhaDaChave>();
  for (const linha of [...((asValidas.data ?? []) as LinhaDaChave[]), ...((asEncerradas.data ?? []) as LinhaDaChave[])]) {
    porId.set(linha.id, linha);
  }
  return [...porId.values()]
    /* Conexão parada há mais de um mês além do prazo dela já contou a história. */
    .filter((linha) => {
      if (linha.origem !== "conexao" || linha.revogada_em || !linha.renovacao_expira_em) return true;
      return new Date(linha.renovacao_expira_em).getTime() > Date.now() - HISTORIA_POR_MS;
    })
    .sort((a, b) => b.criada_em.localeCompare(a.criada_em))
    .map((linha) => paraALista(linha, acesso.user.id));
}

export async function criarChave(
  acesso: Extract<AcessoDoPainel, { ok: true }>,
  dados: { nome: string; escopos: Escopo[]; expiraEm: string | null }
): Promise<{ ok: true; chave: string; registro: ChaveNaLista } | { ok: false; erro: string }> {
  const daPessoa = () =>
    acesso.admin
      .from("chaves_de_acesso")
      .select("id", { count: "exact", head: true })
      .eq("company_id", acesso.empresa.companyId)
      .eq("usuario_id", acesso.user.id)
      /* Só as chaves coladas à mão entram nos tetos: conexão pelo botão tem o teto dela. */
      .eq("origem", "chave");

  /* Dois tetos. O de chaves ativas conta só o que ainda vale: chave vencida
     some da lista depois de um mês, e um limite que conta o que a pessoa não
     enxerga seria uma parede invisível. O de criações por dia impede o ciclo
     de criar e revogar sem parar, que encheria a lista e o banco. */
  const [ativas, criadasHoje] = await Promise.all([
    daPessoa().is("revogada_em", null).or(`expira_em.is.null,expira_em.gt.${new Date().toISOString()}`),
    daPessoa().gt("criada_em", new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()),
  ]);
  if (ativas.error || criadasHoje.error) return { ok: false, erro: "Não foi possível criar a chave. Tente de novo." };
  if ((ativas.count ?? 0) >= MAXIMO_DE_CHAVES_ATIVAS) {
    return { ok: false, erro: `Você já tem ${MAXIMO_DE_CHAVES_ATIVAS} chaves ativas. Revogue uma que não usa mais antes de criar outra.` };
  }
  if ((criadasHoje.count ?? 0) >= MAXIMO_DE_CHAVES_POR_DIA) {
    return { ok: false, erro: "Você já criou muitas chaves nas últimas 24 horas. Tente de novo amanhã." };
  }

  const nova = gerarChave(segredoDoServidor("ktalogu-assistentes-fichas"));
  const { data, error } = await acesso.admin
    .from("chaves_de_acesso")
    .insert({
      company_id: acesso.empresa.companyId,
      dono_id: acesso.empresa.ownerId,
      usuario_id: acesso.user.id,
      usuario_email: acesso.user.email ?? null,
      nome: dados.nome,
      prefixo: nova.prefixo,
      hash: nova.hash,
      escopos: dados.escopos,
      expira_em: dados.expiraEm,
    })
    .select(COLUNAS)
    .single();
  if (error || !data) return { ok: false, erro: "Não foi possível criar a chave. Tente de novo." };

  return { ok: true, chave: nova.chave, registro: paraALista(data as LinhaDaChave, acesso.user.id) };
}

/**
 * Revogar não apaga: a chave fica na lista como revogada, com a história dela.
 * Ela para de valer no pedido seguinte, porque a revogação é lida do banco em
 * todo pedido; não há nada guardado na memória para desfazer.
 */
export async function revogarChave(acesso: Extract<AcessoDoPainel, { ok: true }>, id: string): Promise<boolean> {
  let consulta = acesso.admin
    .from("chaves_de_acesso")
    .update({ revogada_em: new Date().toISOString(), revogada_por: acesso.user.id })
    .eq("id", id)
    .eq("company_id", acesso.empresa.companyId)
    .is("revogada_em", null);
  if (!acesso.verTodas) consulta = consulta.eq("usuario_id", acesso.user.id);

  const { data, error } = await consulta.select("id");
  if (error) throw new Error(error.message);
  return (data ?? []).length > 0;
}

export type AtividadeNaLista = {
  id: number;
  quando: string;
  chave: string | null;
  de: string | null;
  ferramenta: string;
  tipo: "leitura" | "previa" | "escrita";
  ok: boolean;
  resumo: string | null;
  erro: string | null;
  cliente: string | null;
};

export async function listarAtividade(acesso: Extract<AcessoDoPainel, { ok: true }>, limite: number): Promise<AtividadeNaLista[]> {
  let consulta = acesso.admin
    .from("atividade_dos_assistentes")
    .select("id,criada_em,ferramenta,tipo,ok,resumo,erro,cliente,usuario_id,chaves_de_acesso(nome,usuario_email)")
    .eq("company_id", acesso.empresa.companyId)
    .order("criada_em", { ascending: false })
    .limit(limite);
  if (!acesso.verTodas) consulta = consulta.eq("usuario_id", acesso.user.id);

  const { data, error } = await consulta;
  if (error) throw new Error(error.message);

  type Linha = {
    id: number;
    criada_em: string;
    ferramenta: string;
    tipo: "leitura" | "previa" | "escrita";
    ok: boolean;
    resumo: string | null;
    erro: string | null;
    cliente: string | null;
    chaves_de_acesso?: { nome?: string | null; usuario_email?: string | null } | Array<{ nome?: string | null; usuario_email?: string | null }> | null;
  };
  return ((data ?? []) as unknown as Linha[]).map((linha) => {
    const chave = Array.isArray(linha.chaves_de_acesso) ? linha.chaves_de_acesso[0] : linha.chaves_de_acesso;
    return {
      id: linha.id,
      quando: linha.criada_em,
      chave: chave?.nome ?? null,
      de: chave?.usuario_email ?? null,
      ferramenta: linha.ferramenta,
      tipo: linha.tipo,
      ok: linha.ok,
      resumo: linha.resumo,
      erro: linha.erro,
      cliente: linha.cliente,
    };
  });
}
