/**
 * Nota de devolução: o cliente devolveu o que comprou.
 *
 * A empresa emite uma nota de ENTRADA que espelha a venda — mesmos produtos,
 * mesma tributação — referenciando a nota original. Desde a NT 2025.002 a
 * referência é item a item (`sourceDocument`), não só no topo da nota.
 *
 * A tributação não é recalculada de propósito: a devolução tem de devolver
 * exatamente os impostos que saíram na venda. Por isso os valores do item
 * original são reaproveitados e só reduzidos na proporção do que voltou.
 */

import type { AmbienteFiscal } from "@/lib/fiscal/ambiente";
import { centavos, reais } from "@/lib/fiscal/dinheiro";

import { cfopDaDevolucao, textoParaNota } from "@/lib/fiscal/devolucao-regras";

import { carregarEmissor, type Admin } from "./base";
import { carregarNota, enviarNota, ErroFiscal, type NotaGuardada } from "./notas";

export type ItemParaDevolver = {
  indice: number;
  descricao: string;
  quantidade: number;
  unidade: string | null;
  valorUnitario: number;
  valorTotal: number;
};

export type NotaParaDevolver = {
  id: string;
  numero: number | null;
  serie: string | null;
  chave: string | null;
  emitidaEm: string | null;
  destinatario: { nome: string | null; documento: string | null } | null;
  itens: ItemParaDevolver[];
  jaDevolvida: Array<{ id: string; numero: number | null; status: string; created_at: string }>;
};

const numero = (valor: unknown, padrao = 0) => {
  const n = Number(valor);
  return Number.isFinite(n) ? n : padrao;
};

export async function notaParaDevolver(admin: Admin, input: { empresaId: string; notaId: string }): Promise<NotaParaDevolver> {
  const nota = await carregarNota(admin, input.notaId);
  if (nota.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (nota.modelo !== "nfe") throw new ErroFiscal("Só a nota de produto tem devolução. Cupom fiscal se resolve por cancelamento.", 409);
  if (nota.status !== "autorizada") throw new ErroFiscal("Só nota autorizada pode ser devolvida.", 409);

  const payload = (nota.payload ?? {}) as Record<string, unknown>;
  const itens = Array.isArray(payload.items) ? (payload.items as Array<Record<string, unknown>>) : [];
  const destinatario = (nota.destinatario ?? null) as { nome?: string | null; documento?: string | null } | null;

  const { data: devolucoes } = await admin
    .from("fiscal_notas")
    .select("id,numero,status,created_at")
    .eq("nota_origem_id", nota.id)
    .order("created_at", { ascending: false });

  return {
    id: nota.id,
    numero: nota.numero,
    serie: nota.serie,
    chave: nota.chave,
    emitidaEm: nota.autorizada_em ?? nota.created_at,
    destinatario: destinatario ? { nome: destinatario.nome ?? null, documento: destinatario.documento ?? null } : null,
    itens: itens.map((item, indice) => ({
      indice,
      descricao: String(item.description ?? `Item ${indice + 1}`),
      quantidade: numero(item.quantity, 1),
      unidade: item.unit ? String(item.unit) : null,
      valorUnitario: numero(item.unitAmount),
      valorTotal: numero(item.totalAmount),
    })),
    jaDevolvida: (devolucoes ?? []).map((d) => ({
      id: String(d.id),
      numero: d.numero as number | null,
      status: String(d.status),
      created_at: String(d.created_at),
    })),
  };
}

/** Reduz um valor do item original na proporção do que está voltando. */
function proporcional(valor: unknown, fracao: number) {
  const n = Number(valor);
  if (!Number.isFinite(n)) return null;
  return reais(Math.round(centavos(n) * fracao));
}

function impostosProporcionais(taxes: Record<string, unknown> | null | undefined, fracao: number) {
  if (!taxes || typeof taxes !== "object") return undefined;
  const copia: Record<string, unknown> = {};
  for (const [nome, bloco] of Object.entries(taxes)) {
    if (!bloco || typeof bloco !== "object") {
      copia[nome] = bloco;
      continue;
    }
    const grupo: Record<string, unknown> = {};
    for (const [campo, valor] of Object.entries(bloco as Record<string, unknown>)) {
      /* Alíquota e código não mudam com a quantidade; base e valor, sim. */
      const proporcionalmente = /amount$|baseTax$|^baseTax/i.test(campo) && !/Rate$/i.test(campo);
      grupo[campo] = proporcionalmente ? proporcional(valor, fracao) ?? valor : valor;
    }
    copia[nome] = grupo;
  }
  return copia;
}

export async function emitirDevolucao(
  admin: Admin,
  input: {
    empresaId: string;
    ambiente: AmbienteFiscal;
    notaId: string;
    itens: Array<{ indice: number; quantidade: number }>;
    motivo: string;
    usuarioId: string | null;
  }
): Promise<NotaGuardada> {
  const original = await carregarNota(admin, input.notaId);
  if (original.empresa_id !== input.empresaId) throw new ErroFiscal("Nota não encontrada.", 404);
  if (original.status !== "autorizada" || original.modelo !== "nfe") {
    throw new ErroFiscal("Só nota de produto autorizada pode ser devolvida.", 409);
  }
  if (!original.chave) throw new ErroFiscal("A nota original ainda não tem chave de acesso.", 409);

  const motivo = String(input.motivo ?? "").replace(/\s+/g, " ").trim();
  if (motivo.length < 5) throw new ErroFiscal("Escreva o motivo da devolução.", 400);

  const emissor = await carregarEmissor(admin, input.empresaId, input.ambiente);
  if (!emissor?.spedy_empresa_id || !emissor.chave_cifrada) throw new ErroFiscal("Configure a empresa antes de devolver.", 409);

  const payload = (original.payload ?? {}) as Record<string, unknown>;
  const itensOriginais = Array.isArray(payload.items) ? (payload.items as Array<Record<string, unknown>>) : [];
  if (!itensOriginais.length) throw new ErroFiscal("A nota original não tem itens para devolver.", 409);

  const mesmoEstado = String(payload.destination ?? "internal") === "internal";
  let total = 0;
  const itens = input.itens
    .map((escolha) => {
      const original = itensOriginais[escolha.indice];
      if (!original) return null;
      const quantidadeOriginal = numero(original.quantity, 1) || 1;
      const quantidade = Math.min(Math.max(0, numero(escolha.quantidade)), quantidadeOriginal);
      if (quantidade <= 0) return null;
      const fracao = quantidade / quantidadeOriginal;
      const valorTotal = reais(Math.round(centavos(numero(original.totalAmount)) * fracao));
      total += centavos(valorTotal);
      return {
        ...original,
        cfop: cfopDaDevolucao(numero(original.cfop), mesmoEstado),
        quantity: quantidade,
        quantityTax: quantidade,
        totalAmount: valorTotal,
        taxes: impostosProporcionais(original.taxes as Record<string, unknown>, fracao),
        sourceDocument: { accessKey: original.accessKey ?? null, itemNumber: escolha.indice + 1 },
      };
    })
    .filter(Boolean) as Array<Record<string, unknown>>;
  if (!itens.length) throw new ErroFiscal("Escolha ao menos um item para devolver.", 400);

  /* A chave de origem é a mesma para todos os itens: a da nota que voltou. */
  for (const item of itens) {
    item.sourceDocument = { ...(item.sourceDocument as Record<string, unknown>), accessKey: original.chave };
  }

  /* Devolução que a SEFAZ recusou continua ocupando o lugar desta no pedido.
     Corrigir e mandar de novo reaproveita a mesma linha, em vez de encher a
     lista de tentativas. */
  const { data: anterior } = await admin
    .from("fiscal_notas")
    .select("id,status")
    .eq("nota_origem_id", original.id)
    .eq("ambiente", input.ambiente)
    .in("status", ["preparando", "rejeitada", "erro"])
    .order("created_at", { ascending: false })
    .limit(1)
    .maybeSingle();

  const id = anterior?.id ? String(anterior.id) : crypto.randomUUID();
  const novoPayload: Record<string, unknown> = {
    ...payload,
    integrationId: id,
    issue: true,
    operationType: "incoming",
    purposeType: "devolution",
    operationNature: "Devolução de venda",
    items: itens,
    referencedDocuments: [{ accessKey: original.chave }],
    /* Devolução não tem pagamento, mas o grupo `pag` é obrigatório na NF-e: vai
       com "sem pagamento", que é o que a SEFAZ espera nesse caso. */
    payments: [{ method: "noPayment", amount: 0 }],
    additionalInformation: textoParaNota(
      [`Devolucao da nota ${original.numero ?? ""}: ${motivo}`.trim(), `Chave de origem: ${original.chave}`].filter(Boolean).join(" | ")
    ).slice(0, 1000),
  };
  /* A numeração da venda não vale para a devolução. */
  delete novoPayload.number;
  delete novoPayload.accessKey;
  delete novoPayload.code;
  delete novoPayload.duplicates;
  delete novoPayload.billing;
  delete novoPayload.total;

  const linha = {
    id,
    empresa_id: input.empresaId,
    ambiente: input.ambiente,
    emissor_id: emissor.id,
    modelo: "nfe",
    ambiente_nota: original.ambiente_nota,
    origem: "devolucao",
    pedido_id: original.pedido_id,
    /* A trava "uma nota ativa por pedido" existe para a venda não sair duas
       vezes. A devolução é outro documento do mesmo pedido, então entra com
       grupo próprio — senão o banco recusa a segunda nota. */
    grupo: `devolucao:${original.id.slice(0, 8)}`,
    nota_origem_id: original.id,
    status: "preparando",
    valor: reais(total),
    conta_credito: original.conta_credito,
    destinatario: original.destinatario,
    payload: novoPayload,
    emitida_por: input.usuarioId,
  };
  const { error } = anterior?.id
    ? await admin
        .from("fiscal_notas")
        .update({ ...linha, mensagem: null, mensagem_codigo: null, updated_at: new Date().toISOString() })
        .eq("id", id)
    : await admin.from("fiscal_notas").insert(linha);
  if (error) throw new ErroFiscal(`Falha ao registrar a devolução: ${error.message}`, 500);

  return enviarNota(admin, id, { emitir: true, ator: input.usuarioId });
}
