import type { EscalaoIrps, ParametrosFiscaisRH } from "./parametros-fiscais-rh.tsx";
import type { RubricaSalarial } from "./rubricas-salariais.tsx";
import {
  CODIGO_IRPS, CODIGO_INSS_EMPREGADOR, CODIGO_INSS_TRABALHADOR, CODIGO_SALARIO_BASE,
  CODIGO_HORA_EXTRA_UTIL, CODIGO_HORA_EXTRA_SABADO, CODIGO_HORA_EXTRA_DOMINGO,
  CODIGO_HORA_EXTRA_FERIADO_NACIONAL, CODIGO_HORA_EXTRA_FERIADO_LOCAL, CODIGO_HORA_EXTRA_TOLERANCIA,
  CODIGO_DESCONTO_SAIDA_ANTECIPADA,
} from "./rubricas-salariais.tsx";
import type { ApuramentoHorasMensal } from "./calculo-horas-trabalho.tsx";

// ========== MOTOR DE CALCULO SALARIAL (Fase 3 — scaffolding) ==========
//
// Funcoes puras (sem kv.*, sem I/O), testaveis isoladamente, conforme
// desenhado no plano do modulo. Cada funcao recusa-se a calcular (lanca
// erro claro) em vez de usar um valor adivinhado quando os parametros
// fiscais necessarios ainda nao foram confirmados — ver
// parametros-fiscais-rh.tsx para o porque de INSS/IRPS ainda estarem null.
//
// A questao de saber se o IRPS incide sobre o salario bruto ou sobre o
// liquido de INSS tambem e um parametro configuravel
// (parametros.irpsIncideSobreRendimentoBruto), nao uma escolha embutida no
// codigo — calcularFolhaSalarial() recusa-se a compor os dois descontos
// enquanto este campo for null, exactamente como para os valores numericos.

function arredondar(valor: number): number {
  return Math.round(valor * 100) / 100;
}

export class ParametroFiscalNaoConfirmadoError extends Error {
  constructor(campo: string) {
    super(`Não é possível calcular: "${campo}" ainda não foi confirmado para este ano (ver parametros-fiscais-rh.tsx).`);
    this.name = "ParametroFiscalNaoConfirmadoError";
  }
}

// Lista tudo o que falta confirmar para um ano poder suportar processamento
// salarial real. Usar antes de expor qualquer ecra de processamento —
// nunca deixar o utilizador tentar processar uma folha real com isto
// incompleto.
export function validarParametrosFiscaisCompletos(parametros: ParametrosFiscaisRH): string[] {
  const emFalta: string[] = [];

  if (parametros.inssTaxaTrabalhadorPercentual === null) {
    emFalta.push("Taxa de INSS do trabalhador");
  }
  if (parametros.inssTaxaEmpregadorPercentual === null) {
    emFalta.push("Taxa de INSS do empregador");
  }
  if (!parametros.irpsEscaloes || parametros.irpsEscaloes.length === 0) {
    emFalta.push("Escalões de IRPS");
  }
  if (parametros.irpsIncideSobreRendimentoBruto === null) {
    emFalta.push("Metodologia do IRPS (incide sobre o bruto ou sobre o líquido de INSS)");
  }

  return emFalta;
}

// Desconto de INSS a cargo do trabalhador. Base de incidencia: salario
// bruto, ate ao tecto salarial se este existir (nenhuma fonte consultada
// confirmou um tecto — ver parametros-fiscais-rh.tsx).
//
// Conforme a Nota n.º 246/INSS/GAB-DG/432/2024 (citada pela RSM
// Moçambique, "Guia Fiscal 2026"), participação nos lucros, dividendos,
// férias e similares NÃO integram esta base — o chamador desta função é
// responsável por já ter excluído essas parcelas do valor de `salarioBase`
// recebido aqui.
export function calcularDescontoINSSTrabalhador(salarioBase: number, parametros: ParametrosFiscaisRH): number {
  if (parametros.inssTaxaTrabalhadorPercentual === null) {
    throw new ParametroFiscalNaoConfirmadoError("Taxa de INSS do trabalhador");
  }

  const baseIncidencia = parametros.inssTectoSalarialMzn !== null
    ? Math.min(salarioBase, parametros.inssTectoSalarialMzn)
    : salarioBase;

  return arredondar(baseIncidencia * (parametros.inssTaxaTrabalhadorPercentual / 100));
}

// Encargo de INSS a cargo do empregador (nao e descontado ao trabalhador,
// e um custo adicional da empresa) — mesma base de incidencia.
export function calcularEncargoINSSEmpregador(salarioBase: number, parametros: ParametrosFiscaisRH): number {
  if (parametros.inssTaxaEmpregadorPercentual === null) {
    throw new ParametroFiscalNaoConfirmadoError("Taxa de INSS do empregador");
  }

  const baseIncidencia = parametros.inssTectoSalarialMzn !== null
    ? Math.min(salarioBase, parametros.inssTectoSalarialMzn)
    : salarioBase;

  return arredondar(baseIncidencia * (parametros.inssTaxaEmpregadorPercentual / 100));
}

function encontrarEscalaoIrps(rendimentoColectavel: number, escaloes: EscalaoIrps[]): EscalaoIrps | null {
  return escaloes.find(
    (escalao) =>
      rendimentoColectavel >= escalao.limiteInferior &&
      (escalao.limiteSuperior === null || rendimentoColectavel <= escalao.limiteSuperior),
  ) ?? null;
}

// Metodologia confirmada (independentemente dos valores exactos dos
// escaloes, que continuam por confirmar): taxa marginal aplicada a
// totalidade do rendimento colectavel do escalao correspondente, deduzida
// a "parcela a abater" desse escalao. Fonte: pagina "Taxas IRPS" do site
// oficial da Autoridade Tributaria de Moçambique.
export function calcularDescontoIRPS(rendimentoColectavel: number, parametros: ParametrosFiscaisRH): number {
  if (!parametros.irpsEscaloes || parametros.irpsEscaloes.length === 0) {
    throw new ParametroFiscalNaoConfirmadoError("Escalões de IRPS");
  }

  const escalao = encontrarEscalaoIrps(rendimentoColectavel, parametros.irpsEscaloes);
  if (!escalao) {
    throw new Error(`Nenhum escalão de IRPS cobre o rendimento colectável de ${rendimentoColectavel} MZN`);
  }

  const impostoBruto = rendimentoColectavel * (escalao.taxaPercentual / 100);
  return arredondar(Math.max(0, impostoBruto - (escalao.parcelaFixaDeduzir ?? 0)));
}

export interface LinhaFolhaPagamento {
  rubricaId: string;
  codigo: string;
  descricao: string;
  tipo: "remuneratoria" | "desconto" | "encargo_patronal";
  valor: number;
}

export interface RubricaManualEntrada {
  rubricaId: string;
  valor: number;
  justificativa?: string;
}

export interface ResultadoCalculoSalarial {
  salarioBase: number;
  linhas: LinhaFolhaPagamento[];
  totalRemuneracoes: number;
  descontoINSSTrabalhador: number;
  encargoINSSEmpregador: number;
  rendimentoColectavelIRPS: number;
  descontoIRPS: number;
  totalDescontos: number;
  totalEncargosPatronais: number;
  salarioLiquido: number;
}

// Motor de calculo baseado em Rubricas Salariais (ver rubricas-salariais.tsx)
// — substitui o calculo fixo anterior (so salarioBase -> INSS -> IRPS) por
// uma composicao de linhas configuraveis, mantendo a MESMA logica de
// INSS/IRPS ja verificada (calcularDescontoINSSTrabalhador/
// calcularEncargoINSSEmpregador/calcularDescontoIRPS acima, inalteradas).
//
// Limitacoes desta iteracao (deliberadas — ver rubricas-salariais.tsx):
//   - Rubricas "valor_fixo" aplicam-se por igual a todos os colaboradores.
//   - Rubricas "sistema" de horas extra/faltas/atrasos so tem calculo
//     automatico se `apuramentoHoras` for fornecido (ver
//     calculo-horas-trabalho.tsx, ligado a partir de folha-pagamento.tsx) —
//     entram com valor 0 caso contrario, para nao quebrar chamadores antigos.
//   - Rubricas "manual" so geram linha se um valor for fornecido em
//     `rubricasManuais` para este processamento.
export function calcularFolhaSalarial(
  salarioBase: number,
  rubricasAtivas: RubricaSalarial[],
  rubricasManuais: RubricaManualEntrada[],
  parametros: ParametrosFiscaisRH,
  apuramentoHoras?: ApuramentoHorasMensal,
): ResultadoCalculoSalarial {
  const emFalta = validarParametrosFiscaisCompletos(parametros);
  if (emFalta.length > 0) {
    throw new Error(`Não é possível processar a folha salarial — parâmetros por confirmar: ${emFalta.join(", ")}.`);
  }

  const linhas: LinhaFolhaPagamento[] = [];
  const VALORES_SISTEMA_HORAS: Record<string, number> = apuramentoHoras
    ? {
        [CODIGO_HORA_EXTRA_UTIL]: apuramentoHoras.valorHorasExtraUteis,
        [CODIGO_HORA_EXTRA_SABADO]: apuramentoHoras.valorHorasExtraSabado,
        [CODIGO_HORA_EXTRA_DOMINGO]: apuramentoHoras.valorHorasExtraDomingo,
        [CODIGO_HORA_EXTRA_FERIADO_NACIONAL]: apuramentoHoras.valorHorasExtraFeriadoNacional,
        [CODIGO_HORA_EXTRA_FERIADO_LOCAL]: apuramentoHoras.valorHorasExtraFeriadoLocal,
        [CODIGO_HORA_EXTRA_TOLERANCIA]: apuramentoHoras.valorHorasExtraTolerancia,
        DESCFALTA: apuramentoHoras.valorDescontoFalta,
        DESCATRASO: apuramentoHoras.valorDescontoAtraso,
        [CODIGO_DESCONTO_SAIDA_ANTECIPADA]: apuramentoHoras.valorDescontoSaidaAntecipada,
      }
    : {};

  for (const rubrica of rubricasAtivas) {
    if (rubrica.codigo === CODIGO_SALARIO_BASE) {
      linhas.push({ rubricaId: rubrica.id, codigo: rubrica.codigo, descricao: rubrica.descricao, tipo: rubrica.tipo, valor: arredondar(salarioBase) });
      continue;
    }

    if ([CODIGO_INSS_TRABALHADOR, CODIGO_INSS_EMPREGADOR, CODIGO_IRPS].includes(rubrica.codigo)) {
      continue; // calculadas mais abaixo, depois de se saber a base tributavel
    }

    if (rubrica.modoCalculo === "valor_fixo") {
      linhas.push({ rubricaId: rubrica.id, codigo: rubrica.codigo, descricao: rubrica.descricao, tipo: rubrica.tipo, valor: arredondar(rubrica.valorFixo || 0) });
      continue;
    }

    if (rubrica.modoCalculo === "percentual_rubrica") {
      const linhaBase = linhas.find((l) => l.rubricaId === rubrica.rubricaBaseId);
      const valor = linhaBase ? arredondar(linhaBase.valor * ((rubrica.percentual || 0) / 100)) : 0;
      linhas.push({ rubricaId: rubrica.id, codigo: rubrica.codigo, descricao: rubrica.descricao, tipo: rubrica.tipo, valor });
      continue;
    }

    if (rubrica.modoCalculo === "sistema" && rubrica.codigo in VALORES_SISTEMA_HORAS) {
      linhas.push({ rubricaId: rubrica.id, codigo: rubrica.codigo, descricao: rubrica.descricao, tipo: rubrica.tipo, valor: arredondar(VALORES_SISTEMA_HORAS[rubrica.codigo]) });
      continue;
    }

    if (rubrica.modoCalculo === "sistema" && [
      CODIGO_HORA_EXTRA_UTIL, CODIGO_HORA_EXTRA_SABADO, CODIGO_HORA_EXTRA_DOMINGO,
      CODIGO_HORA_EXTRA_FERIADO_NACIONAL, CODIGO_HORA_EXTRA_FERIADO_LOCAL, CODIGO_HORA_EXTRA_TOLERANCIA,
      "DESCFALTA", "DESCATRASO", CODIGO_DESCONTO_SAIDA_ANTECIPADA,
    ].includes(rubrica.codigo)) {
      linhas.push({ rubricaId: rubrica.id, codigo: rubrica.codigo, descricao: rubrica.descricao, tipo: rubrica.tipo, valor: 0 });
      continue;
    }

    if (rubrica.modoCalculo === "manual") {
      const entrada = rubricasManuais.find((m) => m.rubricaId === rubrica.id);
      if (entrada) {
        linhas.push({ rubricaId: rubrica.id, codigo: rubrica.codigo, descricao: rubrica.descricao, tipo: rubrica.tipo, valor: arredondar(entrada.valor) });
      }
    }
  }

  const somaLinhas = (predicado: (l: LinhaFolhaPagamento) => boolean) =>
    linhas.filter(predicado).reduce((soma, l) => soma + l.valor, 0);

  const baseInss = arredondar(
    somaLinhas((l) => l.tipo === "remuneratoria" && rubricasAtivas.find((r) => r.id === l.rubricaId)?.incideInss === true),
  );
  const baseIrpsBruta = arredondar(
    somaLinhas((l) => l.tipo === "remuneratoria" && rubricasAtivas.find((r) => r.id === l.rubricaId)?.incideIrps === true),
  );

  const descontoINSSTrabalhador = calcularDescontoINSSTrabalhador(baseInss, parametros);
  const encargoINSSEmpregador = calcularEncargoINSSEmpregador(baseInss, parametros);

  const rendimentoColectavelIRPS = parametros.irpsIncideSobreRendimentoBruto
    ? baseIrpsBruta
    : arredondar(baseIrpsBruta - descontoINSSTrabalhador);

  const descontoIRPS = calcularDescontoIRPS(rendimentoColectavelIRPS, parametros);

  const rubricaInssTrab = rubricasAtivas.find((r) => r.codigo === CODIGO_INSS_TRABALHADOR);
  const rubricaInssEmp = rubricasAtivas.find((r) => r.codigo === CODIGO_INSS_EMPREGADOR);
  const rubricaIrps = rubricasAtivas.find((r) => r.codigo === CODIGO_IRPS);

  if (rubricaInssTrab) linhas.push({ rubricaId: rubricaInssTrab.id, codigo: rubricaInssTrab.codigo, descricao: rubricaInssTrab.descricao, tipo: "desconto", valor: descontoINSSTrabalhador });
  if (rubricaInssEmp) linhas.push({ rubricaId: rubricaInssEmp.id, codigo: rubricaInssEmp.codigo, descricao: rubricaInssEmp.descricao, tipo: "encargo_patronal", valor: encargoINSSEmpregador });
  if (rubricaIrps) linhas.push({ rubricaId: rubricaIrps.id, codigo: rubricaIrps.codigo, descricao: rubricaIrps.descricao, tipo: "desconto", valor: descontoIRPS });

  const totalRemuneracoes = arredondar(somaLinhas((l) => l.tipo === "remuneratoria"));
  const totalDescontos = arredondar(somaLinhas((l) => l.tipo === "desconto"));
  const totalEncargosPatronais = arredondar(somaLinhas((l) => l.tipo === "encargo_patronal"));
  const salarioLiquido = arredondar(totalRemuneracoes - totalDescontos);

  if (salarioLiquido < 0) {
    throw new Error("O salário líquido calculado é negativo — reveja os descontos manuais atribuídos");
  }

  return {
    salarioBase,
    linhas,
    totalRemuneracoes,
    descontoINSSTrabalhador,
    encargoINSSEmpregador,
    rendimentoColectavelIRPS,
    descontoIRPS,
    totalDescontos,
    totalEncargosPatronais,
    salarioLiquido,
  };
}
