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

import { canCompany, getCompanyContext, type CompanyContext, type CompanyPermission } from "@/lib/company-context";
import { ambienteFiscal, chaveDaTitular, pendenciasDoServidor, type AmbienteFiscal } from "@/lib/fiscal/ambiente";
import { decifrar, decifrarObjeto } from "@/lib/fiscal/cofre";
import type { EmissorGuardado, SegredosDoEmissor } from "@/lib/fiscal/emissor";
import { createAdminClient, createClient } from "@/lib/supabase-server";

import { assinaturaDaEmpresa } from "./assinatura-da-empresa";

/**
 * O que toda rota fiscal faz antes de qualquer coisa: quem é, de que empresa,
 * pode ou não pode, e em qual conta da Spedy este servidor fala.
 *
 * As tabelas fiscais não têm política para o navegador: quem lê e grava é a
 * chave de serviço, DEPOIS desta conferência. `empresaId` é sempre o dono da
 * empresa — o mesmo `user_id` dos pedidos, clientes e produtos.
 */

export type Admin = SupabaseClient;

export type ContextoFiscal = {
  admin: Admin;
  user: User;
  ctx: CompanyContext;
  empresaId: string;
  ambiente: AmbienteFiscal;
  pode: (permissao: CompanyPermission) => boolean;
};

export function erroJson(mensagem: string, status: number, extra?: Record<string, unknown>) {
  return NextResponse.json({ error: mensagem, ...(extra ?? {}) }, { status });
}

export async function abrirContextoFiscal(
  permissao: CompanyPermission | CompanyPermission[] | null
): Promise<ContextoFiscal | NextResponse> {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) return erroJson("Sua sessão expirou. Entre de novo.", 401);

  const ctx = await getCompanyContext(supabase, user);
  if (ctx.membership && ctx.membership.active === false) return erroJson("Seu acesso a esta empresa está desativado.", 403);

  const pode = (chave: CompanyPermission) => canCompany(ctx, user.id, chave);
  const exigidas = permissao === null ? [] : Array.isArray(permissao) ? permissao : [permissao];
  if (exigidas.length && !exigidas.some(pode)) {
    return erroJson("Você não tem permissão para isso. Peça ao responsável da empresa.", 403);
  }

  const admin = createAdminClient();
  const empresaId = ctx.ownerId || user.id;

  /* Assinatura do Ktalogu vencida fecha o módulo inteiro — para o dono e para
     quem trabalha na empresa dele. O middleware olha a linha de quem está
     logado; aqui olhamos a da EMPRESA, que é quem paga. */
  const assinatura = await assinaturaDaEmpresa(admin, empresaId);
  if (!assinatura.ativa) return erroJson(assinatura.mensagem, 402, { assinatura: assinatura.situacao });

  return {
    admin,
    user,
    ctx,
    empresaId,
    ambiente: ambienteFiscal(),
    pode,
  };
}

export function servidorPronto(): string | null {
  const faltando = pendenciasDoServidor();
  if (!faltando.length) return null;
  console.warn(`[fiscal] emissão desligada neste servidor: falta ${faltando.join(", ")}`);
  return "A emissão de notas fiscais está indisponível no momento. Fale com o suporte do Ktalogu.";
}

/* ---------------- Emissor ---------------- */

export async function carregarEmissor(admin: Admin, empresaId: string, ambiente: AmbienteFiscal) {
  const { data, error } = await admin
    .from("fiscal_emissores")
    .select("*")
    .eq("empresa_id", empresaId)
    .eq("ambiente", ambiente)
    .maybeSingle();
  if (error) throw new Error(`Falha ao ler a empresa emissora: ${error.message}`);
  return (data as EmissorGuardado | null) ?? null;
}

export function chaveDoEmissor(emissor: Pick<EmissorGuardado, "chave_cifrada">): string {
  if (!emissor.chave_cifrada) throw new Error("A empresa ainda não terminou o cadastro para emitir notas.");
  return decifrar(emissor.chave_cifrada);
}

export function segredosDoEmissor(emissor: Pick<EmissorGuardado, "segredos_cifrados">): SegredosDoEmissor {
  try {
    return decifrarObjeto(emissor.segredos_cifrados) as SegredosDoEmissor;
  } catch {
    return {};
  }
}

export function spedyDaTitular(ambiente: AmbienteFiscal) {
  const chave = chaveDaTitular();
  if (!chave) throw new Error("A emissão de notas fiscais está indisponível no momento.");
  return { chave, ambiente };
}

export function spedyDoEmissor(emissor: EmissorGuardado) {
  return { chave: chaveDoEmissor(emissor), ambiente: emissor.ambiente };
}

/**
 * A chave para GERIR uma empresa na Spedy: certificado e configurações, tudo
 * que mora em `/companies/{id}/...`. Só a TITULAR entra ali. A chave da
 * própria empresa serve para as notas dela e leva 403 "Acesso não autorizado"
 * nesses endereços — foi assim que o certificado dos primeiros clientes em
 * produção foi recusado. No sandbox passou batido porque a única empresa
 * testada era a titular, cuja chave é a mesma.
 */
export function spedyDaGestao(emissor: Pick<EmissorGuardado, "ambiente">) {
  return spedyDaTitular(emissor.ambiente);
}

/* ---------------- Regras, saldo, eventos e fila ---------------- */

export type RegrasDaPlataforma = {
  limite_emissores: number;
  notas_teste_por_mes: number;
  aviso_saldo_baixo: number;
  plano_spedy_notas: number;
  plano_spedy_valor: number;
  plano_spedy_nota_excedente: number;
  vendas_abertas: boolean;
};

export async function regrasDaPlataforma(admin: Admin): Promise<RegrasDaPlataforma> {
  const { data } = await admin.from("fiscal_regras").select("*").eq("id", 1).maybeSingle();
  const r = (data ?? {}) as Record<string, unknown>;
  const numero = (valor: unknown, padrao: number) => (Number.isFinite(Number(valor)) ? Number(valor) : padrao);
  return {
    limite_emissores: numero(r.limite_emissores, 30),
    notas_teste_por_mes: numero(r.notas_teste_por_mes, 30),
    aviso_saldo_baixo: numero(r.aviso_saldo_baixo, 10),
    plano_spedy_notas: numero(r.plano_spedy_notas, 1000),
    plano_spedy_valor: numero(r.plano_spedy_valor, 129),
    plano_spedy_nota_excedente: numero(r.plano_spedy_nota_excedente, 0.161),
    vendas_abertas: r.vendas_abertas !== false,
  };
}

export type SaldoDoMes = {
  competencia: string;
  entradas: number;
  usadas: number;
  reservadas: number;
  disponiveis: number;
};

export async function competenciaAtual(admin: Admin): Promise<string> {
  const { data, error } = await admin.rpc("fiscal_competencia", {});
  if (error || !data) {
    const agora = new Intl.DateTimeFormat("en-CA", { timeZone: "America/Sao_Paulo", year: "numeric", month: "2-digit" }).format(new Date());
    return `${agora}-01`;
  }
  return String(data);
}

export async function saldoDoMes(admin: Admin, empresaId: string, ambiente: AmbienteFiscal, competencia?: string): Promise<SaldoDoMes> {
  const mes = competencia ?? (await competenciaAtual(admin));
  const { data, error } = await admin.rpc("fiscal_saldo", { p_empresa: empresaId, p_ambiente: ambiente, p_competencia: mes });
  if (error) throw new Error(`Falha ao ler o saldo de notas: ${error.message}`);
  const r = (data ?? {}) as Record<string, unknown>;
  return {
    competencia: String(r.competencia ?? mes),
    entradas: Number(r.entradas ?? 0),
    usadas: Number(r.usadas ?? 0),
    reservadas: Number(r.reservadas ?? 0),
    disponiveis: Number(r.disponiveis ?? 0),
  };
}

export async function registrarEvento(
  admin: Admin,
  evento: {
    notaId: string;
    empresaId: string;
    tipo: string;
    titulo: string;
    detalhe?: string | null;
    dados?: unknown;
    ator?: string | null;
  }
) {
  await admin.from("fiscal_eventos").insert({
    nota_id: evento.notaId,
    empresa_id: evento.empresaId,
    tipo: evento.tipo,
    titulo: evento.titulo,
    detalhe: evento.detalhe ?? null,
    dados: evento.dados ?? null,
    ator: evento.ator ?? null,
  });
}

export type TipoDeTarefa = "emitir" | "emitir_pedido" | "consultar" | "baixar_arquivos" | "cancelar" | "conferir_compra";

export async function agendarTarefa(
  admin: Admin,
  tarefa: {
    ambiente: AmbienteFiscal;
    tipo: TipoDeTarefa;
    empresaId?: string | null;
    notaId?: string | null;
    compraId?: string | null;
    pedidoId?: string | null;
    dados?: Record<string, unknown>;
    emSegundos?: number;
  }
) {
  const executarEm = new Date(Date.now() + Math.max(0, tarefa.emSegundos ?? 0) * 1000).toISOString();
  const { error } = await admin.rpc("fiscal_agendar", {
    p_ambiente: tarefa.ambiente,
    p_tipo: tarefa.tipo,
    p_empresa: tarefa.empresaId ?? null,
    p_nota: tarefa.notaId ?? null,
    p_compra: tarefa.compraId ?? null,
    p_pedido: tarefa.pedidoId ?? null,
    p_dados: tarefa.dados ?? {},
    p_executar_em: executarEm,
  });
  if (error) console.error("[fiscal] falha ao agendar tarefa", tarefa.tipo, error.message);
}
