import * as kv from "./kv_store.tsx";
import * as kvAtomic from "./kv_atomic.tsx";
import { createLogger } from "./logger.tsx";
import { buscarColaboradorPorId, listarColaboradores } from "./colaboradores.tsx";
import { obterParametrosFiscaisRH } from "./parametros-fiscais-rh.tsx";
import { calcularFolhaSalarial, type ResultadoCalculoSalarial, type RubricaManualEntrada } from "./calculo-salarial.tsx";
import { listarRubricasSalariais } from "./rubricas-salariais.tsx";
import { obterPeriodoFolhaPagamento } from "./periodo-folha-pagamento.tsx";
import { apurarHorasColaboradorMes, type CreditoBancoHorasProposto } from "./calculo-horas-trabalho.tsx";
import { registarMovimentoBancoHoras, removerMovimentosDaFolha } from "./banco-horas.tsx";

const logger = createLogger('FolhaPagamento');

// ========== TIPOS ==========

export interface FolhaPagamento extends ResultadoCalculoSalarial {
  id: string;
  empresaId: string;
  colaboradorId: string;
  ano: number;
  mes: number; // 1-12
  numeroRecibo: string; // sequencial por empresa+ano (ex: 0001), nao e documento fiscal, nao usa document-numbering.tsx
  avisosHorasExtra?: string[]; // limites do Art. 99 n.3 excedidos — ver calculo-horas-trabalho.tsx (nao bloqueia o processamento)
  processadoPor: string;
  dataProcessamento: string;
  dataCriacao: string;
  dataAtualizacao?: string;
}

const FOLHA_PREFIX = "folha_pagamento:";

function chavePeriodoUnico(empresaId: string, colaboradorId: string, ano: number, mes: number): string {
  return `folha_pagamento_periodo:${empresaId}:${colaboradorId}:${ano}:${String(mes).padStart(2, "0")}`;
}

async function reservarNumeroRecibo(empresaId: string, ano: number): Promise<string> {
  const existentes = await listarFolhasPagamento(empresaId, { ano });
  let candidato = existentes.length + 1;

  for (let tentativa = 0; tentativa < 500; tentativa += 1) {
    const numero = String(candidato).padStart(4, "0");
    const chave = `folha_pagamento_numero:${empresaId}:${ano}:${numero}`;
    const resultado = await kvAtomic.createIfAbsent(chave, { reservedAt: new Date().toISOString() });

    if (resultado.created) {
      return numero;
    }

    candidato += 1;
  }

  throw new Error("Não foi possível gerar um número de recibo único");
}

// ========== FUNCOES ==========

export async function processarFolhaPagamento(
  empresaId: string,
  colaboradorId: string,
  ano: number,
  mes: number,
  processadoPor: string,
  rubricasManuais: RubricaManualEntrada[] = [],
): Promise<{ success: boolean; data?: FolhaPagamento; error?: string }> {
  try {
    if (!empresaId || !colaboradorId) {
      return { success: false, error: "Empresa e colaborador são obrigatórios" };
    }

    if (!Number.isInteger(ano) || !Number.isInteger(mes) || mes < 1 || mes > 12) {
      return { success: false, error: "Ano ou mês inválido" };
    }

    if (!processadoPor?.trim()) {
      return { success: false, error: "É obrigatório identificar quem processou a folha" };
    }

    const periodo = await obterPeriodoFolhaPagamento(empresaId, ano, mes);
    if (periodo.estado === "fechado") {
      return { success: false, error: `O período ${mes}/${ano} está fechado. Reabra-o primeiro (com motivo) para processar ou corrigir folhas.` };
    }

    const colaborador = await buscarColaboradorPorId(colaboradorId);
    if (!colaborador || colaborador.empresaId !== empresaId) {
      return { success: false, error: "Colaborador não encontrado nesta empresa" };
    }

    if (colaborador.estado !== "ativo") {
      return { success: false, error: `Colaborador não está activo (estado: ${colaborador.estado})` };
    }

    const chavePeriodo = chavePeriodoUnico(empresaId, colaboradorId, ano, mes);
    const reserva = await kvAtomic.createIfAbsent(chavePeriodo, { reservedAt: new Date().toISOString() });
    if (!reserva.created) {
      return { success: false, error: `Já existe uma folha de pagamento processada para ${colaborador.nome} em ${mes}/${ano}` };
    }

    let resultadoCalculo: ResultadoCalculoSalarial;
    let avisosHorasExtra: string[] = [];
    let creditosBancoHorasPropostos: CreditoBancoHorasProposto[] = [];
    try {
      const parametros = await obterParametrosFiscaisRH(ano);
      const rubricasAtivas = await listarRubricasSalariais(empresaId, { ativa: true });
      const apuramentoHoras = await apurarHorasColaboradorMes(empresaId, colaborador, ano, mes, parametros);
      avisosHorasExtra = apuramentoHoras.avisos;
      creditosBancoHorasPropostos = apuramentoHoras.creditosBancoHorasPropostos;
      resultadoCalculo = calcularFolhaSalarial(colaborador.salarioBase, rubricasAtivas, rubricasManuais, parametros, apuramentoHoras);
    } catch (erroCalculo) {
      // Liberta a reserva do periodo, para nao bloquear uma tentativa futura
      // quando os parametros ja estiverem confirmados.
      await kvAtomic.deleteKey(chavePeriodo);
      return { success: false, error: erroCalculo.message };
    }

    const numeroRecibo = await reservarNumeroRecibo(empresaId, ano);
    const id = `folha_pagamento_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

    const folha: FolhaPagamento = {
      id,
      empresaId,
      colaboradorId,
      ano,
      mes,
      numeroRecibo,
      ...resultadoCalculo,
      avisosHorasExtra: avisosHorasExtra.length > 0 ? avisosHorasExtra : undefined,
      processadoPor: processadoPor.trim(),
      dataProcessamento: new Date().toISOString(),
      dataCriacao: new Date().toISOString(),
    };

    await kv.set(`${FOLHA_PREFIX}${id}`, folha);
    await kv.set(chavePeriodo, id);

    // Credita o Banco de Horas so DEPOIS da folha estar persistida com
    // sucesso — a mesma reserva atomica do periodo garante que isto corre
    // uma unica vez por colaborador/mes.
    for (const credito of creditosBancoHorasPropostos) {
      await registarMovimentoBancoHoras({
        empresaId,
        colaboradorId,
        data: credito.data,
        tipo: "credito",
        origem: credito.origem,
        minutos: credito.minutos,
        folhaPagamentoId: id,
        registadoPor: processadoPor.trim(),
      });
    }

    logger.info(`Folha de pagamento processada: ${colaborador.nome} - ${mes}/${ano} - recibo ${numeroRecibo}`);

    return { success: true, data: folha };
  } catch (error) {
    logger.error("Erro ao processar folha de pagamento:", error);
    return { success: false, error: error.message };
  }
}

export interface ResultadoProcessamentoLote {
  processadas: FolhaPagamento[];
  jaExistentes: string[]; // nomes de colaboradores ja processados neste periodo
  erros: Array<{ colaboradorNome: string; erro: string }>;
}

export async function processarFolhaPagamentoEmLote(
  empresaId: string,
  ano: number,
  mes: number,
  processadoPor: string,
): Promise<ResultadoProcessamentoLote> {
  const colaboradoresAtivos = await listarColaboradores(empresaId, { ativo: true, estado: "ativo" });

  const resultado: ResultadoProcessamentoLote = {
    processadas: [],
    jaExistentes: [],
    erros: [],
  };

  for (const colaborador of colaboradoresAtivos) {
    const resposta = await processarFolhaPagamento(empresaId, colaborador.id, ano, mes, processadoPor);

    if (resposta.success && resposta.data) {
      resultado.processadas.push(resposta.data);
    } else if (resposta.error?.includes("Já existe uma folha")) {
      resultado.jaExistentes.push(colaborador.nome);
    } else {
      resultado.erros.push({ colaboradorNome: colaborador.nome, erro: resposta.error || "Erro desconhecido" });
    }
  }

  return resultado;
}

// Reprocessamento em lote — util quando os parametros fiscais (INSS/IRPS)
// de um ano sao corrigidos DEPOIS de ja existirem folhas processadas com
// os valores antigos. Nao ha deteccao automatica entre empresas (os
// parametros sao globais, mas cada empresa decide por si quando
// reprocessar); cabe ao utilizador desta empresa accionar isto quando
// souber que os parametros mudaram. Elimina e reprocessa cada folha do
// mes, reaproveitando as mesmas guardas de periodo fechado ja existentes
// em deletarFolhaPagamento/processarFolhaPagamento.
export async function reprocessarFolhasPagamentoEmLote(
  empresaId: string,
  ano: number,
  mes: number,
  processadoPor: string,
): Promise<ResultadoProcessamentoLote> {
  const folhasExistentes = await listarFolhasPagamento(empresaId, { ano, mes });

  const resultado: ResultadoProcessamentoLote = {
    processadas: [],
    jaExistentes: [],
    erros: [],
  };

  for (const folha of folhasExistentes) {
    const colaborador = await buscarColaboradorPorId(folha.colaboradorId);
    const nomeColaborador = colaborador?.nome || folha.colaboradorId;

    const remocao = await deletarFolhaPagamento(folha.id);
    if (!remocao.success) {
      resultado.erros.push({ colaboradorNome: nomeColaborador, erro: remocao.error || "Não foi possível eliminar a folha anterior" });
      continue;
    }

    const resposta = await processarFolhaPagamento(empresaId, folha.colaboradorId, ano, mes, processadoPor);
    if (resposta.success && resposta.data) {
      resultado.processadas.push(resposta.data);
    } else {
      resultado.erros.push({ colaboradorNome: nomeColaborador, erro: resposta.error || "Erro desconhecido" });
    }
  }

  return resultado;
}

export async function buscarFolhaPagamentoPorId(id: string): Promise<FolhaPagamento | null> {
  try {
    const folha = await kv.get(`${FOLHA_PREFIX}${id}`);
    return folha as FolhaPagamento | null;
  } catch (error) {
    logger.error("Erro ao buscar folha de pagamento:", error);
    return null;
  }
}

export async function listarFolhasPagamento(
  empresaId: string,
  filtros?: { colaboradorId?: string; ano?: number; mes?: number },
): Promise<FolhaPagamento[]> {
  try {
    const todas = await kv.getByPrefix<FolhaPagamento>(FOLHA_PREFIX);

    let folhas = todas.filter((item) => item && item.empresaId === empresaId);

    if (filtros?.colaboradorId) {
      folhas = folhas.filter((item) => item.colaboradorId === filtros.colaboradorId);
    }

    if (filtros?.ano !== undefined) {
      folhas = folhas.filter((item) => item.ano === filtros.ano);
    }

    if (filtros?.mes !== undefined) {
      folhas = folhas.filter((item) => item.mes === filtros.mes);
    }

    folhas.sort((a, b) => (b.ano - a.ano) || (b.mes - a.mes) || a.numeroRecibo.localeCompare(b.numeroRecibo));

    return folhas;
  } catch (error) {
    logger.error("Erro ao listar folhas de pagamento:", error);
    return [];
  }
}

// Eliminar so deve servir para corrigir um processamento errado (ex.:
// parametros fiscais estavam errados quando foi processado) — liberta
// tambem a reserva do periodo, para permitir reprocessar.
export async function deletarFolhaPagamento(id: string): Promise<{ success: boolean; error?: string }> {
  try {
    const folha = await buscarFolhaPagamentoPorId(id);
    if (!folha) {
      return { success: false, error: "Folha de pagamento não encontrada" };
    }

    const periodo = await obterPeriodoFolhaPagamento(folha.empresaId, folha.ano, folha.mes);
    if (periodo.estado === "fechado") {
      return { success: false, error: `O período ${folha.mes}/${folha.ano} está fechado. Reabra-o primeiro (com motivo) para eliminar folhas.` };
    }

    await kv.del(`${FOLHA_PREFIX}${id}`);
    await kvAtomic.deleteKey(chavePeriodoUnico(folha.empresaId, folha.colaboradorId, folha.ano, folha.mes));
    await removerMovimentosDaFolha(id);

    logger.info(`Folha de pagamento deletada: ${id}`);

    return { success: true };
  } catch (error) {
    logger.error("Erro ao deletar folha de pagamento:", error);
    return { success: false, error: error.message };
  }
}
