import type { AmbienteFiscal } from "../fiscal/ambiente.ts";
import { chamarSpedy } from "./cliente.ts";

/**
 * Os endpoints da Spedy que o Ktalogu usa, tipados pelo OpenAPI v1.
 *
 * Ficam de fora de propósito os clientes (`/customers`) e produtos
 * (`/products`) da Spedy: eles pertencem à CONTA e são compartilhados entre
 * todas as empresas dela — usar misturaria os clientes de uma gráfica com os de
 * outra. Destinatário e itens vão dentro de cada nota. A API de vendas
 * (`/orders`) também fica de fora: a tributação dela vem do painel da Spedy,
 * que os clientes do Ktalogu não acessam.
 */

export type ModeloFiscal = "nfe" | "nfce" | "nfse";

export const CAMINHO_DO_MODELO: Record<ModeloFiscal, string> = {
  nfe: "/product-invoices",
  nfce: "/consumer-invoices",
  nfse: "/service-invoices",
};

export const NOME_DO_MODELO: Record<ModeloFiscal, string> = {
  nfe: "NF-e",
  nfce: "NFC-e",
  nfse: "NFS-e",
};

export type SpedyStatusDaNota =
  | "created"
  | "enqueued"
  | "received"
  | "authorized"
  | "inContingent"
  | "rejected"
  | "canceled"
  | "denied"
  | "removed"
  | "disabled";

export type SpedyDetalheDoProcessamento = {
  status?: "processing" | "success" | "failed" | string | null;
  message?: string | null;
  code?: string | null;
  on?: string | null;
};

export type SpedyNota = {
  id: string;
  integrationId?: string | null;
  status: SpedyStatusDaNota | string;
  model?: "productInvoice" | "consumerInvoice" | "serviceInvoice" | string;
  environmentType?: "production" | "development" | "simulation" | string;
  issuedOn?: string | null;
  effectiveDate?: string | null;
  receiver?: { name?: string | null; federalTaxNumber?: string | null; email?: string | null } | null;
  company?: {
    name?: string | null;
    legalName?: string | null;
    federalTaxNumber?: string | null;
    stateTaxNumber?: string | null;
    cityTaxNumber?: string | null;
  } | null;
  authorization?: { date?: string | null; protocol?: string | null; digestValue?: string | null } | null;
  cancellation?: { date?: string | null; protocol?: string | null; justification?: string | null } | null;
  amount?: number | null;
  number?: number | null;
  series?: string | null;
  accessKey?: string | null;
  issueType?: string | null;
  contingency?: { date?: string | null; reason?: string | null } | null;
  processingDetail?: SpedyDetalheDoProcessamento | null;
  rps?: { number?: number | null; series?: string | null } | null;
};

export type SpedyEndereco = {
  street?: string | null;
  number?: string | null;
  district?: string | null;
  postalCode?: string | null;
  additionalInformation?: string | null;
  city?: { code?: string | number | null; name?: string | null; state?: string | null } | null;
  country?: { name: string; code?: number | null } | string | null;
};

export type SpedyEmpresa = {
  id: string;
  name?: string | null;
  legalName?: string | null;
  federalTaxNumber?: string | null;
  stateTaxNumber?: string | null;
  cityTaxNumber?: string | null;
  email?: string | null;
  phone?: string | null;
  address?: SpedyEndereco | null;
  taxRegime?: string | null;
  specialTaxRegime?: string | null;
  simplesNacionalTaxRegime?: string | null;
  apiCredentials?: { apiKey?: string | null } | null;
  economicActivities?: Array<{ code?: string | null; type?: string | null }> | null;
};

export type SpedyEmpresaEditavel = {
  name: string;
  legalName: string;
  federalTaxNumber: string;
  stateTaxNumber?: string | null;
  cityTaxNumber?: string | null;
  email?: string | null;
  phone?: string | null;
  address: {
    street: string;
    number: string;
    district: string;
    postalCode: string;
    additionalInformation?: string | null;
    city: { code: string; name: string; state: string };
    country?: { name: string; code?: number } | null;
  };
  taxRegime: string;
  specialTaxRegime?: string | null;
  simplesNacionalTaxRegime?: string | null;
  economicActivities?: Array<{ code: string; type: "main" | "secondary" }>;
};

export type SpedyCertificado = {
  id: string;
  expirationAt: string;
  subject?: string | null;
  issuer?: string | null;
  isActive: boolean;
};

export type SpedyBlocoDeConfiguracao = Record<string, unknown>;

export type SpedyConfiguracoes = {
  serviceInvoice?: SpedyBlocoDeConfiguracao | null;
  productInvoice?: SpedyBlocoDeConfiguracao | null;
  consumerInvoice?: SpedyBlocoDeConfiguracao | null;
  general?: SpedyBlocoDeConfiguracao | null;
};

export type SpedyOpcoesDoProvedor = {
  rps?: boolean;
  series?: boolean;
  batchNumber?: boolean;
  invoiceNumber?: boolean;
  requiresNumberInSequence?: boolean;
  federalServiceCode?: boolean;
  nbsCode?: boolean;
  cnaeCode?: boolean;
  cityServiceCode?: boolean;
  newBatchNumberForEachAttempt?: boolean;
  removeAccents?: boolean;
  supportsTaxReform?: boolean;
  useNationalLayout?: boolean;
  nationalTaxationCode?: boolean;
  useSimplesNacionalAnnex?: boolean;
  authentication?: "certificate" | "userName" | "token" | string;
  requiresDigitalCertificate?: boolean;
};

export type SpedyCidade = {
  code: string;
  name: string;
  state: string;
  provider?: { code?: string | null; name?: string | null; options?: SpedyOpcoesDoProvedor | null } | null;
  nationalServiceInvoiceRegimes?: string | null;
};

export type SpedyPaginado<T> = {
  totalCount?: number | null;
  pageCount?: number | null;
  pageSize?: number | null;
  hasNext?: boolean;
  items?: T[] | null;
};

export type SpedyExemploDaCidade = {
  code: number;
  name?: string | null;
  state?: string | null;
  examples?: Array<{ provider?: string | null; sourceIssuedOn?: string | null; payload?: Record<string, unknown> | null }> | null;
};

export type SpedyWebhook = { id: string; event?: string | null; url?: string | null; enabled?: boolean };

export type SpedyInutilizacao = {
  id: string;
  initialNumber: number;
  finalNumber: number;
  series?: string | null;
  reason?: string | null;
  protocolNumber?: string | null;
  status: "inProcessing" | "registered" | "rejected" | "error" | string;
  messageCode?: string | null;
  messageText?: string | null;
};

type Contexto = { chave: string; ambiente?: AmbienteFiscal };

/* ---------------- Empresas (chave da titular) ---------------- */

export function criarEmpresa(ctx: Contexto, dados: SpedyEmpresaEditavel) {
  /* Não é repetível: se a resposta se perder, a empresa existe e a chave dela
     (mostrada uma vez) se perdeu junto. O chamador trata procurando pelo CNPJ. */
  return chamarSpedy<SpedyEmpresa>("/companies", { ...ctx, metodo: "POST", corpo: dados, repetivel: false, tentativas: 1 });
}

export function alterarEmpresa(ctx: Contexto, id: string, dados: SpedyEmpresaEditavel) {
  return chamarSpedy<SpedyEmpresa>(`/companies/${id}`, { ...ctx, metodo: "PUT", corpo: { id, ...dados }, repetivel: true });
}

export function obterEmpresa(ctx: Contexto, id: string) {
  return chamarSpedy<SpedyEmpresa>(`/companies/${id}`, ctx);
}

export function excluirEmpresa(ctx: Contexto, id: string) {
  return chamarSpedy<{ success?: boolean }>(`/companies/${id}`, { ...ctx, metodo: "DELETE", repetivel: true });
}

export async function listarTodasAsEmpresas(ctx: Contexto) {
  const todas: SpedyEmpresa[] = [];
  for (let pagina = 1; pagina <= 20; pagina += 1) {
    const resposta = await chamarSpedy<SpedyPaginado<SpedyEmpresa> | SpedyEmpresa[]>(`/companies?page=${pagina}&pageSize=100`, ctx);
    const itens = Array.isArray(resposta) ? resposta : resposta?.items ?? [];
    todas.push(...itens);
    const temMais = !Array.isArray(resposta) && Boolean(resposta?.hasNext);
    if (!temMais || itens.length === 0) break;
  }
  return todas;
}

/* ---------------- Certificado e configurações (chave da empresa) ---------------- */

export function enviarCertificado(ctx: Contexto, empresaId: string, arquivo: Blob, nomeDoArquivo: string, senha: string) {
  const formulario = new FormData();
  formulario.append("certificateFile", arquivo, nomeDoArquivo);
  formulario.append("password", senha);
  return chamarSpedy<SpedyCertificado>(`/companies/${empresaId}/certificates`, {
    ...ctx,
    metodo: "POST",
    formulario,
    repetivel: false,
    tentativas: 1,
    timeoutMs: 60000,
  });
}

export function listarCertificados(ctx: Contexto, empresaId: string) {
  return chamarSpedy<SpedyCertificado[]>(`/companies/${empresaId}/certificates`, ctx);
}

export function obterConfiguracoes(ctx: Contexto, empresaId: string) {
  return chamarSpedy<SpedyConfiguracoes>(`/companies/${empresaId}/settings`, ctx);
}

export function alterarConfiguracoes(ctx: Contexto, empresaId: string, blocos: SpedyConfiguracoes) {
  return chamarSpedy<SpedyConfiguracoes>(`/companies/${empresaId}/settings`, { ...ctx, metodo: "PUT", corpo: blocos, repetivel: true });
}

/* ---------------- Notas ---------------- */

export function criarNota(ctx: Contexto, modelo: ModeloFiscal, payload: Record<string, unknown>) {
  /* Repetível: o integrationId faz o segundo POST atualizar a mesma nota. */
  return chamarSpedy<SpedyNota>(CAMINHO_DO_MODELO[modelo], { ...ctx, metodo: "POST", corpo: payload, repetivel: true });
}

export function obterNota(ctx: Contexto, modelo: ModeloFiscal, id: string, opcoes: { timeoutMs?: number; tentativas?: number } = {}) {
  return chamarSpedy<SpedyNota>(`${CAMINHO_DO_MODELO[modelo]}/${id}`, { ...ctx, ...opcoes });
}

export async function procurarNotaPorIntegracao(ctx: Contexto, modelo: ModeloFiscal, integrationId: string) {
  const resposta = await chamarSpedy<SpedyPaginado<SpedyNota>>(
    `${CAMINHO_DO_MODELO[modelo]}?integrationId=${encodeURIComponent(integrationId)}&page=1&pageSize=5`,
    ctx
  );
  return (resposta?.items ?? []).find((nota) => nota.integrationId === integrationId) ?? null;
}

export function cancelarNota(ctx: Contexto, modelo: ModeloFiscal, id: string, motivo: string) {
  return chamarSpedy<SpedyNota>(`${CAMINHO_DO_MODELO[modelo]}/${id}`, {
    ...ctx,
    metodo: "DELETE",
    corpo: { reason: motivo },
    repetivel: true,
  });
}

export function consultarNaAutoridade(ctx: Contexto, modelo: ModeloFiscal, id: string) {
  return chamarSpedy<SpedyNota>(`${CAMINHO_DO_MODELO[modelo]}/${id}/check-status`, { ...ctx, metodo: "POST", repetivel: true });
}

export function reemitirNota(ctx: Contexto, modelo: ModeloFiscal, id: string) {
  return chamarSpedy<unknown>(`${CAMINHO_DO_MODELO[modelo]}/${id}/issue`, { ...ctx, metodo: "POST", corpo: {}, repetivel: true });
}

export function cartaDeCorrecao(ctx: Contexto, id: string, texto: string) {
  return chamarSpedy<Record<string, unknown>>(`/product-invoices/${id}/corrections`, {
    ...ctx,
    metodo: "POST",
    corpo: { letter: texto },
    repetivel: false,
    tentativas: 1,
  });
}

export function reenviarEmail(ctx: Contexto, modelo: ModeloFiscal, id: string) {
  return chamarSpedy<unknown>(`${CAMINHO_DO_MODELO[modelo]}/${id}/resend-email`, { ...ctx, metodo: "POST", repetivel: false, tentativas: 1 });
}

export function baixarArquivoDaNota(ctx: Contexto, modelo: ModeloFiscal, id: string, tipo: "pdf" | "xml", previa = false) {
  const sufixo = previa ? "?preview=true" : "";
  return chamarSpedy<ArrayBuffer>(`${CAMINHO_DO_MODELO[modelo]}/${id}/${tipo}${sufixo}`, { ...ctx, binario: true, timeoutMs: 45000 });
}

export function inutilizarNumeracao(
  ctx: Contexto,
  modelo: "nfe" | "nfce",
  dados: { series: number; initialNumber: number; finalNumber: number; reason: string }
) {
  return chamarSpedy<SpedyInutilizacao>(`${CAMINHO_DO_MODELO[modelo]}/disablements`, {
    ...ctx,
    metodo: "POST",
    corpo: dados,
    repetivel: false,
    tentativas: 1,
  });
}

/* ---------------- NFS-e: cidades ---------------- */

export async function procurarCidade(ctx: Contexto, codigoIbge: string) {
  const resposta = await chamarSpedy<SpedyPaginado<SpedyCidade>>(
    `/service-invoices/cities?code=${encodeURIComponent(codigoIbge)}&page=1&pageSize=5`,
    ctx
  );
  return (resposta?.items ?? []).find((cidade) => String(cidade.code) === String(codigoIbge)) ?? null;
}

export function exemploDaCidade(ctx: Contexto, codigoIbge: string) {
  return chamarSpedy<SpedyExemploDaCidade>(`/service-invoices/cities/${encodeURIComponent(codigoIbge)}/payload-example`, ctx);
}

/* ---------------- Webhooks (conta) ---------------- */

export async function listarWebhooks(ctx: Contexto) {
  const resposta = await chamarSpedy<SpedyPaginado<SpedyWebhook> | SpedyWebhook[]>("/webhooks?page=1&pageSize=50", ctx);
  return Array.isArray(resposta) ? resposta : resposta?.items ?? [];
}

export function criarWebhook(ctx: Contexto, evento: string, url: string) {
  return chamarSpedy<SpedyWebhook>("/webhooks", { ...ctx, metodo: "POST", corpo: { event: evento, url }, repetivel: false, tentativas: 1 });
}

export function habilitarWebhook(ctx: Contexto, id: string) {
  return chamarSpedy<unknown>(`/webhooks/${id}/enable`, { ...ctx, metodo: "PUT", repetivel: true });
}

export function segredoDosWebhooks(ctx: Contexto) {
  return chamarSpedy<{ secret?: string | null; createdAt?: string; previousSecretExpiresAt?: string | null }>("/webhooks/secret", ctx);
}

/* ---------------- Notas recebidas (as que terceiros emitem contra o CNPJ) ---------------- */

export type SpedyNotaRecebida = {
  id: string;
  model?: "productInvoice" | "serviceInvoice" | string;
  environmentType?: string | null;
  accessKey?: string | null;
  nsu?: number | null;
  isComplete?: boolean;
  status?: "authorized" | "denied" | "canceled" | string;
  issuedOn?: string | null;
  amount?: number | null;
  operationType?: string | null;
  issuer?: { name?: string | null; legalName?: string | null; federalTaxNumber?: string | null } | null;
  manifestation?: { status?: string | null; date?: string | null; justification?: string | null } | null;
  creationTime?: string | null;
  lastModificationTime?: string | null;
};

export type SpedyPaginaPorCursor<T> = { items?: T[] | null; nextCursor?: string | null; hasNext?: boolean };

export type SpedySincronizacaoDeRecebidas = {
  lastNsu?: number | null;
  lastSyncAt?: string | null;
  nextAllowedSyncAt?: string | null;
  lastAttemptOutcome?: string | null;
  lastAttemptMessage?: string | null;
  lastAttemptAt?: string | null;
};

/** `nfe` = NF-e recebida (produto); `nfse` = NFS-e tomada (serviço). */
export type ModeloRecebido = "nfe" | "nfse";

const CAMINHO_DO_RECEBIDO: Record<ModeloRecebido, string> = {
  nfe: "/inbound-product-invoices",
  nfse: "/inbound-service-invoices",
};

export function listarNotasRecebidas(
  ctx: Contexto,
  modelo: ModeloRecebido,
  opcoes: { cursor?: string | null; pageSize?: number; initialDate?: string | null; endDate?: string | null } = {}
) {
  const busca = new URLSearchParams();
  busca.set("pageSize", String(Math.min(100, Math.max(1, opcoes.pageSize ?? 50))));
  if (opcoes.cursor) busca.set("cursor", opcoes.cursor);
  if (opcoes.initialDate) busca.set("initialDate", opcoes.initialDate);
  if (opcoes.endDate) busca.set("endDate", opcoes.endDate);
  return chamarSpedy<SpedyPaginaPorCursor<SpedyNotaRecebida>>(`${CAMINHO_DO_RECEBIDO[modelo]}?${busca.toString()}`, ctx);
}

export function obterNotaRecebida(ctx: Contexto, modelo: ModeloRecebido, id: string) {
  return chamarSpedy<SpedyNotaRecebida>(`${CAMINHO_DO_RECEBIDO[modelo]}/${id}`, ctx);
}

/**
 * Manifestação do destinatário. É síncrona: a SEFAZ responde na hora e, se
 * recusar, nada fica gravado. `justification` é obrigatória (15 a 255) para
 * desconhecimento e operação não realizada.
 */
export function manifestarNotaRecebida(
  ctx: Contexto,
  id: string,
  dados: { status: "acknowledged" | "confirmed" | "unknown" | "notPerformed"; justification?: string | null }
) {
  return chamarSpedy<SpedyNotaRecebida>(`${CAMINHO_DO_RECEBIDO.nfe}/${id}/manifest`, {
    ...ctx,
    metodo: "POST",
    corpo: { status: dados.status, justification: dados.justification ?? null },
    repetivel: false,
    tentativas: 1,
    timeoutMs: 45000,
  });
}

/**
 * Pede à Spedy que busque agora na distribuição da SEFAZ. A SEFAZ limita a
 * frequência por CNPJ: quando vier 429, o `Retry-After` tem de ser respeitado,
 * senão o CNPJ é bloqueado temporariamente por consumo indevido.
 */
export function sincronizarRecebidas(ctx: Contexto, modelo: ModeloRecebido) {
  return chamarSpedy<unknown>(`${CAMINHO_DO_RECEBIDO[modelo]}/sync`, {
    ...ctx,
    metodo: "POST",
    corpo: {},
    repetivel: false,
    tentativas: 1,
  });
}

export function situacaoDaSincronizacao(ctx: Contexto, modelo: ModeloRecebido) {
  return chamarSpedy<SpedySincronizacaoDeRecebidas>(`${CAMINHO_DO_RECEBIDO[modelo]}/sync-status`, ctx);
}

export function baixarArquivoDaRecebida(ctx: Contexto, modelo: ModeloRecebido, id: string, tipo: "pdf" | "xml") {
  return chamarSpedy<ArrayBuffer>(`${CAMINHO_DO_RECEBIDO[modelo]}/${id}/${tipo}`, { ...ctx, binario: true, timeoutMs: 45000 });
}

/* ---------------- Inutilização de numeração ---------------- */

export function listarInutilizacoes(ctx: Contexto, modelo: "nfe" | "nfce", pagina = 1, porPagina = 20) {
  return chamarSpedy<SpedyPaginado<SpedyInutilizacao>>(
    `${CAMINHO_DO_MODELO[modelo]}/disablements?page=${pagina}&pageSize=${porPagina}`,
    ctx
  );
}

export function obterInutilizacao(ctx: Contexto, modelo: "nfe" | "nfce", id: string) {
  return chamarSpedy<SpedyInutilizacao>(`${CAMINHO_DO_MODELO[modelo]}/disablements/${id}`, ctx);
}

export function xmlDaInutilizacao(ctx: Contexto, modelo: "nfe" | "nfce", id: string) {
  return chamarSpedy<ArrayBuffer>(`${CAMINHO_DO_MODELO[modelo]}/disablements/${id}/xml`, { ...ctx, binario: true, timeoutMs: 45000 });
}
