import * as kv from "./kv_store.tsx";
import * as kvAtomic from "./kv_atomic.tsx";
import { createLogger } from "./logger.tsx";
import { isValidNuit, normalizeNuit } from "./nuit.tsx";
import { registarAlteracoesColaborador } from "./historico-colaborador.tsx";
import { criarComunicacaoINSS } from "./comunicacao-inss.tsx";

const logger = createLogger('Colaboradores');

// ========== TIPOS ==========

// Alinhado com a Lei n.º 13/2023, de 25 de Agosto (Lei do Trabalho em vigor,
// revoga a Lei n.º 23/2007). Combina dois eixos que a lei trata como
// contínuos: duração do contrato (Art. 2.º) e modalidades de trabalho
// (Art. 3.º-A e seguintes) — "efetivo/termo_certo/termo_incerto" são
// alternativas quanto à duração; as restantes são modalidades específicas
// de prestação de trabalho.
export type TipoContratoColaborador =
  | "efetivo" // Por tempo indeterminado
  | "termo_certo" // A prazo certo (máx. 2 anos, renovável até 2 vezes)
  | "termo_incerto" // A prazo incerto, ligado a uma condição (máx. 6 anos)
  | "empreitada" // Regime de empreitada (trabalho por tarefa/obra)
  | "avenca" // Regime de avença (prestação de serviços)
  | "regime_livre" // Regime livre
  | "intermitente" // Trabalho intermitente
  | "teletrabalho" // Teletrabalho
  | "sazonal" // Trabalho sazonal
  | "temporario" // Trabalho temporário (via agenciamento privado de emprego)
  | "tempo_parcial" // Trabalho a tempo parcial
  | "estagio"; // Contrato de aprendizagem/estágio

// Regimes especiais de trabalho por setor (Art. 3.º da Lei n.º 13/2023),
// regulados por legislação complementar específica. NÃO são alternativas ao
// tipo de contrato acima — aplicam-se em simultâneo (ex.: um trabalhador
// mineiro pode ter um contrato efetivo OU a termo certo, e estar sujeito ao
// regime especial do trabalho mineiro ao mesmo tempo). Campo opcional.
export type RegimeEspecialColaborador =
  | "artistico"
  | "desportivo"
  | "domestico"
  | "domicilio"
  | "maritimo"
  | "mineiro"
  | "pesca"
  | "petrolifero"
  | "portuario"
  | "rural"
  | "seguranca_privada";

export type EstadoColaborador = "ativo" | "suspenso" | "cessado";

export type EstadoCivilColaborador = "solteiro" | "casado" | "uniao_facto" | "divorciado" | "viuvo";

export type TipoContaBancaria = "corrente" | "poupanca" | "salario";
export type TitularidadeContaBancaria = "propria" | "conjunta" | "terceiro";

export interface Colaborador {
  id: string;
  empresaId: string; // Multi-tenant: cada empresa tem os seus colaboradores
  numeroFuncionario: string; // Sequencial por empresa (ex: 0001), gerado de forma segura em concorrencia
  nome: string;
  nuit: string; // NUIT obrigatorio em Mocambique (necessario para IRPS)
  numeroIdentificacao?: string; // BI ou Passaporte
  numeroInss?: string;
  dataNascimento?: string;
  genero?: "masculino" | "feminino" | "outro" | "prefere_nao_declarar";
  email?: string;
  telefone?: string;
  telemovel?: string;
  endereco?: string;
  cidade?: string;
  provincia?: string;
  pais: string;
  cargo: string;
  departamento?: string;
  tipoContrato: TipoContratoColaborador;
  regimeEspecial?: RegimeEspecialColaborador;
  dataAdmissao: string;
  dataCessacao?: string;
  salarioBase: number;
  nib?: string; // Numero de Identificacao Bancaria, para pagamento de salario (21 digitos)
  observacoes?: string;

  // ===== Dados bancarios (Grupo 1, secao 3) =====
  banco?: string;
  agenciaBanco?: string;
  numeroConta?: string; // numero de conta do banco (formato proprio de cada banco, distinto do NIB)
  tipoContaBancaria?: TipoContaBancaria;
  titularidadeContaBancaria?: TitularidadeContaBancaria;
  nomeTitularContaBancaria?: string; // se titularidade === 'terceiro'
  nuitTitularContaBancaria?: string;
  relacaoTitularContaBancaria?: string;
  dataAberturaContaBancaria?: string;
  estado: EstadoColaborador;
  ativo: boolean;
  dataCriacao: string;
  dataAtualizacao?: string;

  // ===== Dados pessoais adicionais (Grupo 1) =====
  nacionalidade?: string; // default "Moçambicana"
  numeroPassaporte?: string; // relevante quando nacionalidade != Moçambicana
  paisEmissorPassaporte?: string;
  dataValidadePassaporte?: string;
  tipoAutorizacaoTrabalho?: string; // DIRE / visto de trabalho / autorizacao de residencia
  dataValidadeAutorizacaoTrabalho?: string;
  dataValidadeIdentificacao?: string; // validade do BI/Passaporte (numeroIdentificacao)
  localEmissaoIdentificacao?: string;
  estadoCivil?: EstadoCivilColaborador;
  nomeConjuge?: string;
  nuitConjuge?: string;
  provinciaNaturalidade?: string;
  distritoNaturalidade?: string;
  distrito?: string;
  bairro?: string;
  pontoReferenciaMorada?: string;
  contactoEmergenciaNome?: string;
  contactoEmergenciaTelefone?: string;
  contactoEmergenciaParentesco?: string;
  emailCorporativo?: string;
  fotoUrl?: string; // campo pronto para a Fase de anexos documentais (Grupo 2) — ainda sem UI de upload

  // ===== Dados profissionais adicionais (Grupo 1) =====
  categoriaProfissional?: string;
  seccao?: string; // refinamento dentro do departamento
  centroCusto?: string;
  localTrabalho?: string;
  supervisorId?: string; // referencia a outro Colaborador.id
  sindicalizado?: boolean;
  nomeSindicato?: string;
  numeroSocioSindicato?: string;
  dataFimContrato?: string; // previsao de fim, para termo_certo/termo_incerto
  dataFimPeriodoExperimental?: string; // Art. 12.º da Lei 13/2023 — periodo probatorio, aplica-se a qualquer tipo de contrato
  dataInscricaoInss?: string;

  // ===== Presencas — Grupo A (calendario/feriados) =====
  calendarioTrabalhoId?: string; // referencia a CalendarioTrabalho.id; sem valor = usa o calendario "PADRAO" da empresa
  regimeContinuidade?: boolean; // Art. 105 n.4 da Lei 13/2023 — sector de continuidade, isento da suspensao em feriados/tolerancia de ponto
  sectorContinuidadeAlinea?: string; // alinea a)-m) do Art. 105 n.4 aplicavel, quando regimeContinuidade === true
}

const COLABORADOR_PREFIX = "colaborador:";

// ========== NUMERACAO SEQUENCIAL SEGURA EM CONCORRENCIA ==========
//
// O numero de funcionario e permanente (nunca reinicia por ano), ao contrario
// da numeracao de documentos fiscais (facturas/recibos), por isso nao usa o
// mecanismo de document-numbering.tsx (pensado para series anuais). Em vez
// disso reserva o numero directamente via kv_atomic.createIfAbsent, que e
// atomico (INSERT com chave unica), evitando a falha de concorrencia do
// padrao "contar + 1" usado em fornecedores.tsx/clientes.tsx.
async function reservarNumeroFuncionario(empresaId: string): Promise<string> {
  const colaboradoresExistentes = await listarColaboradores(empresaId);
  let candidato = colaboradoresExistentes.length + 1;

  for (let tentativa = 0; tentativa < 200; tentativa += 1) {
    const numero = String(candidato).padStart(4, "0");
    const chave = `colaborador_numero:${empresaId}:${numero}`;
    const resultado = await kvAtomic.createIfAbsent(chave, {
      reservedAt: new Date().toISOString(),
    });

    if (resultado.created) {
      return numero;
    }

    candidato += 1;
  }

  throw new Error("Nao foi possivel gerar um numero de funcionario unico");
}

// ========== VALIDACAO DE FORMATO BANCARIO ==========
//
// Aceita o NIB mocambicano tal como consta no extrato/cartao do banco (21
// digitos numericos, sem "MZ") OU o IBAN internacional completo ("MZ" + 2
// digitos de controlo + os mesmos 21 digitos = 25 caracteres), validado
// pelo algoritmo MOD-97 (ISO 7064) — universal a todos os paises IBAN, nao
// especifico de Mocambique, por isso nao exige a mesma confirmacao legal
// profissional que INSS/IRPS.
export function isValidNib(nib: string): boolean {
  const limpo = nib.replace(/[\s-]/g, "").toUpperCase();

  if (/^\d{21}$/.test(limpo)) return true;

  if (limpo.length === 25 && /^MZ\d{23}$/.test(limpo)) {
    const rearranjado = limpo.slice(4) + limpo.slice(0, 4);
    const numerico = rearranjado.replace(/[A-Z]/g, (letra) => String(letra.charCodeAt(0) - 55));
    let resto = 0;
    for (const digito of numerico) {
      resto = (resto * 10 + Number(digito)) % 97;
    }
    return resto === 1;
  }

  return false;
}

// ========== FUNCOES DE GESTAO DE COLABORADORES ==========

export async function criarColaborador(
  dados: Omit<Colaborador, "id" | "numeroFuncionario" | "dataCriacao" | "estado">,
): Promise<{ success: boolean; data?: Colaborador; error?: string }> {
  try {
    const nuitNormalizado = normalizeNuit(dados.nuit);

    if (!dados.empresaId || !dados.nome || !nuitNormalizado) {
      return { success: false, error: "Empresa, nome e NUIT são obrigatórios" };
    }

    if (!isValidNuit(nuitNormalizado)) {
      return { success: false, error: "NUIT inválido. Deve conter exatamente 9 dígitos numéricos" };
    }

    if (!dados.cargo?.trim()) {
      return { success: false, error: "Cargo é obrigatório" };
    }

    if (!dados.dataAdmissao) {
      return { success: false, error: "Data de admissão é obrigatória" };
    }

    if (!Number.isFinite(Number(dados.salarioBase)) || Number(dados.salarioBase) < 0) {
      return { success: false, error: "Salário base inválido" };
    }

    if (!dados.banco?.trim()) {
      return { success: false, error: "Banco é obrigatório" };
    }

    if (!dados.tipoContaBancaria) {
      return { success: false, error: "Tipo de conta é obrigatório" };
    }

    if (!dados.nib?.trim()) {
      return { success: false, error: "NIB é obrigatório" };
    }

    if (!isValidNib(dados.nib)) {
      return { success: false, error: "NIB/IBAN inválido. Deve ter 21 dígitos (NIB) ou ser o IBAN completo (MZ + 23 dígitos)" };
    }

    const colaboradorExistente = await buscarColaboradorPorNuit(dados.empresaId, nuitNormalizado);
    if (colaboradorExistente) {
      return { success: false, error: "Já existe um colaborador com este NUIT" };
    }

    const id = `colaborador_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    const numeroFuncionario = await reservarNumeroFuncionario(dados.empresaId);

    const colaborador: Colaborador = {
      ...dados,
      id,
      numeroFuncionario,
      nuit: nuitNormalizado,
      pais: dados.pais || "Moçambique",
      salarioBase: Number(dados.salarioBase),
      estado: "ativo",
      ativo: dados.ativo !== undefined ? dados.ativo : true,
      dataCriacao: new Date().toISOString(),
    };

    await kv.set(`${COLABORADOR_PREFIX}${id}`, colaborador);
    await kv.set(`colaborador_nuit:${dados.empresaId}:${nuitNormalizado}`, id);
    await kv.set(`colaborador_numero:${dados.empresaId}:${numeroFuncionario}`, id);

    await criarComunicacaoINSS({
      empresaId: colaborador.empresaId,
      colaboradorId: colaborador.id,
      colaboradorNome: colaborador.nome,
      tipo: "admissao",
      dataEvento: colaborador.dataAdmissao,
      detalhes: {
        numeroIdentificacao: colaborador.numeroIdentificacao,
        nuit: colaborador.nuit,
        tipoContrato: colaborador.tipoContrato,
        salarioBase: colaborador.salarioBase,
        categoriaProfissional: colaborador.categoriaProfissional,
        cargo: colaborador.cargo,
        departamento: colaborador.departamento,
        localTrabalho: colaborador.localTrabalho,
      },
    });

    logger.info(`Colaborador criado: ${numeroFuncionario} - ${dados.nome}`);

    return { success: true, data: colaborador };
  } catch (error) {
    logger.error("Erro ao criar colaborador:", error);
    return { success: false, error: error.message };
  }
}

export async function buscarColaboradorPorId(id: string): Promise<Colaborador | null> {
  try {
    const colaborador = await kv.get(`${COLABORADOR_PREFIX}${id}`);
    return colaborador as Colaborador | null;
  } catch (error) {
    logger.error("Erro ao buscar colaborador:", error);
    return null;
  }
}

export async function buscarColaboradorPorNuit(empresaId: string, nuit: string): Promise<Colaborador | null> {
  try {
    const colaboradorId = await kv.get(`colaborador_nuit:${empresaId}:${normalizeNuit(nuit)}`);
    if (!colaboradorId) return null;

    return await buscarColaboradorPorId(colaboradorId as string);
  } catch (error) {
    logger.error("Erro ao buscar colaborador por NUIT:", error);
    return null;
  }
}

export async function listarColaboradores(
  empresaId: string,
  filtros?: { ativo?: boolean; estado?: EstadoColaborador },
): Promise<Colaborador[]> {
  try {
    const todosColaboradores = await kv.getByPrefix<Colaborador>(COLABORADOR_PREFIX);

    let colaboradores = todosColaboradores.filter((item) => item && item.empresaId === empresaId);

    if (filtros?.ativo !== undefined) {
      colaboradores = colaboradores.filter((item) => item.ativo === filtros.ativo);
    }

    if (filtros?.estado !== undefined) {
      colaboradores = colaboradores.filter((item) => item.estado === filtros.estado);
    }

    colaboradores.sort((a, b) => a.numeroFuncionario.localeCompare(b.numeroFuncionario));

    return colaboradores;
  } catch (error) {
    logger.error("Erro ao listar colaboradores:", error);
    return [];
  }
}

export async function atualizarColaborador(
  id: string,
  dados: Partial<Colaborador>,
  alteradoPor?: string,
): Promise<{ success: boolean; data?: Colaborador; error?: string }> {
  try {
    const colaborador = await buscarColaboradorPorId(id);

    if (!colaborador) {
      return { success: false, error: "Colaborador não encontrado" };
    }

    const nuitAtualizado = dados.nuit !== undefined ? normalizeNuit(dados.nuit) : undefined;

    if (dados.nuit !== undefined) {
      if (!nuitAtualizado) {
        return { success: false, error: "NUIT é obrigatório" };
      }

      if (!isValidNuit(nuitAtualizado)) {
        return { success: false, error: "NUIT inválido. Deve conter exatamente 9 dígitos numéricos" };
      }
    }

    if (dados.salarioBase !== undefined && (!Number.isFinite(Number(dados.salarioBase)) || Number(dados.salarioBase) < 0)) {
      return { success: false, error: "Salário base inválido" };
    }

    if (dados.nib && !isValidNib(dados.nib)) {
      return { success: false, error: "NIB/IBAN inválido. Deve ter 21 dígitos (NIB) ou ser o IBAN completo (MZ + 23 dígitos)" };
    }

    if (nuitAtualizado && nuitAtualizado !== colaborador.nuit) {
      const nuitExiste = await buscarColaboradorPorNuit(colaborador.empresaId, nuitAtualizado);
      if (nuitExiste) {
        return { success: false, error: "Já existe um colaborador com este NUIT" };
      }

      await kv.del(`colaborador_nuit:${colaborador.empresaId}:${colaborador.nuit}`);
      await kv.set(`colaborador_nuit:${colaborador.empresaId}:${nuitAtualizado}`, id);
    }

    const colaboradorAtualizado: Colaborador = {
      ...colaborador,
      ...dados,
      ...(nuitAtualizado ? { nuit: nuitAtualizado } : {}),
      ...(dados.salarioBase !== undefined ? { salarioBase: Number(dados.salarioBase) } : {}),
      id: colaborador.id,
      numeroFuncionario: colaborador.numeroFuncionario,
      empresaId: colaborador.empresaId,
      dataCriacao: colaborador.dataCriacao,
      dataAtualizacao: new Date().toISOString(),
    };

    await kv.set(`${COLABORADOR_PREFIX}${id}`, colaboradorAtualizado);

    await registarAlteracoesColaborador(colaborador.empresaId, id, colaborador, colaboradorAtualizado, alteradoPor);

    if (dados.salarioBase !== undefined && Number(dados.salarioBase) !== colaborador.salarioBase) {
      await criarComunicacaoINSS({
        empresaId: colaborador.empresaId,
        colaboradorId: id,
        colaboradorNome: colaboradorAtualizado.nome,
        tipo: "alteracao_salarial",
        dataEvento: new Date().toISOString().slice(0, 10),
        detalhes: {
          salarioAnterior: colaborador.salarioBase,
          salarioNovo: colaboradorAtualizado.salarioBase,
        },
      });
    }

    logger.info(`Colaborador atualizado: ${id}`);

    return { success: true, data: colaboradorAtualizado };
  } catch (error) {
    logger.error("Erro ao atualizar colaborador:", error);
    return { success: false, error: error.message };
  }
}

// Cessacao de contrato nunca elimina o registo (historico necessario para
// processamento salarial e obrigacoes fiscais/laborais ja emitidas) — apenas
// muda o estado. Eliminacao fisica so e permitida se nunca houve nenhum
// documento associado (ver deletarColaborador).
export async function cessarColaborador(
  id: string,
  dataCessacao: string,
): Promise<{ success: boolean; data?: Colaborador; error?: string }> {
  if (!dataCessacao) {
    return { success: false, error: "Data de cessação é obrigatória" };
  }

  return await atualizarColaborador(id, {
    estado: "cessado",
    ativo: false,
    dataCessacao,
  });
}

export async function toggleColaborador(
  id: string,
  ativo: boolean,
): Promise<{ success: boolean; data?: Colaborador; error?: string }> {
  return await atualizarColaborador(id, {
    ativo,
    estado: ativo ? "ativo" : "suspenso",
  });
}

export async function deletarColaborador(id: string): Promise<{ success: boolean; error?: string }> {
  try {
    const colaborador = await buscarColaboradorPorId(id);

    if (!colaborador) {
      return { success: false, error: "Colaborador não encontrado" };
    }

    // Esta guarda existe para nao apagar historico com efeitos
    // fiscais/contabilisticos ja lancados (folha de pagamento) nem deixar
    // dependentes/documentos orfaos no KV/Storage.
    const foliasPagamento = (await kv.getByPrefix<any>("folha_pagamento:"))
      .filter((folha) => folha && folha.colaboradorId === id);
    if (foliasPagamento.length > 0) {
      return { success: false, error: "Colaborador possui processamento salarial associado. Cesse o contrato em vez de eliminar." };
    }

    const dependentesAssociados = (await kv.getByPrefix<any>("dependente:"))
      .filter((dependente) => dependente && dependente.colaboradorId === id);
    if (dependentesAssociados.length > 0) {
      return { success: false, error: "Colaborador possui dependentes registados. Elimine-os primeiro ou cesse o contrato em vez de eliminar." };
    }

    const documentosAssociados = (await kv.getByPrefix<any>("documento_colaborador:"))
      .filter((documento) => documento && documento.colaboradorId === id);
    if (documentosAssociados.length > 0) {
      return { success: false, error: "Colaborador possui documentos anexados. Elimine-os primeiro ou cesse o contrato em vez de eliminar." };
    }

    await kv.del(`${COLABORADOR_PREFIX}${id}`);
    await kv.del(`colaborador_nuit:${colaborador.empresaId}:${colaborador.nuit}`);
    await kv.del(`colaborador_numero:${colaborador.empresaId}:${colaborador.numeroFuncionario}`);

    logger.info(`Colaborador deletado: ${id}`);

    return { success: true };
  } catch (error) {
    logger.error("Erro ao deletar colaborador:", error);
    return { success: false, error: error.message };
  }
}
