import { v } from "convex/values";
import { query } from "./_generated/server";
import { Id } from "./_generated/dataModel";
import type { QueryCtx } from "./_generated/server";

/**
 * SISTEMA DE CÁLCULO DE SALDO DE FÉRIAS
 * Suporte a múltiplos regimes de trabalho
 * 
 * ============================================
 * REGRAS CLT (Consolidação das Leis do Trabalho):
 * ============================================
 * - 30 dias de férias por ano trabalhado
 * - Período aquisitivo: 12 meses de trabalho
 * - Período concessivo: 12 meses após aquisitivo
 * - Pode dividir em até 3 períodos
 * - Um período deve ter no mínimo 14 dias
 * - Demais períodos: mínimo 5 dias cada
 * - Abono pecuniário: vender 1/3 das férias (10 dias) - OPCIONAL
 * 
 * ============================================
 * REGRAS SERVIDOR PÚBLICO ESTADUAL DE PERNAMBUCO
 * Lei nº 6.123/1968 - Estatuto dos Funcionários Públicos Civis do Estado de PE
 * ============================================
 * - 30 dias de férias por ano de exercício
 * - Pode dividir em até 2 períodos (NÃO 3)
 * - Nenhum período pode ser inferior a 10 dias (NÃO 5)
 * - NÃO permite abono pecuniário (venda de férias)
 * - Férias devem ser gozadas no ano subsequente
 * - Servidor com mais de 10 anos: pode acumular até 2 períodos
 * - Preferência: férias no período de 20/12 a 10/01 para docentes
 * - Gestante: pode antecipar ou prorrogar férias
 *
 * ============================================
 * REGRAS SERVIDOR PÚBLICO MUNICIPAL
 * ============================================
 * - Seguem as mesmas diretrizes do regime estadual acima
 */

type RegimeTrabalho = "clt" | "estatutario_pe" | "estatutario_federal" | "estatutario_municipal";

// Configurações por regime
const REGIMES_CONFIG = {
  clt: {
    nome: "CLT - Consolidação das Leis do Trabalho",
    maxPeriodos: 3,
    minDiasPeriodo: 5,
    minDiasPeriodoPrincipal: 14,
    abonoPermitido: true,
    maxDiasAbono: 10,
  },
  estatutario_pe: {
    nome: "Servidor Público Estadual de Pernambuco",
    maxPeriodos: 2,
    minDiasPeriodo: 15, // Mínimo 15 dias por período
    minDiasPeriodoPrincipal: null, // Não há essa regra
    abonoPermitido: false,
    maxDiasAbono: 0,
    periodosPermitidos: [15, 30], // Apenas 15 ou 30 dias por período
  },
  estatutario_federal: {
    nome: "Servidor Público Federal",
    maxPeriodos: 3,
    minDiasPeriodo: 5,
    minDiasPeriodoPrincipal: 14,
    abonoPermitido: true,
    maxDiasAbono: 10,
  },
  estatutario_municipal: {
    nome: "Servidor Público Municipal",
    maxPeriodos: 2,
    minDiasPeriodo: 15, // Mínimo 15 dias por período
    minDiasPeriodoPrincipal: null,
    abonoPermitido: false,
    maxDiasAbono: 0,
    periodosPermitidos: [15, 30], // Apenas 15 ou 30 dias por período
  },
};

// Helper: Calcular dias entre duas datas
function calcularDiasEntreDatas(dataInicio: string, dataFim: string): number {
  const inicio = new Date(dataInicio);
  const fim = new Date(dataFim);
  const diffTime = Math.abs(fim.getTime() - inicio.getTime());
  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; // +1 para incluir ambos os dias
  return diffDays;
}

// Helper: Calcular data de fim do período aquisitivo
function calcularDataFimPeriodo(dataAdmissao: string, anosPassados: number): string {
  const dataInicio = new Date(dataAdmissao);
  dataInicio.setFullYear(dataInicio.getFullYear() + anosPassados);
  return dataInicio.toISOString().split('T')[0];
}

// Helper: Obter regime de trabalho do funcionário
async function obterRegimeTrabalho(ctx: QueryCtx, funcionarioId: Id<"funcionarios">): Promise<RegimeTrabalho> {
  const funcionario = await ctx.db.get(funcionarioId);
  return funcionario?.regimeTrabalho || "clt"; // Default CLT
}

// Helper: Calcular saldo dinamicamente baseado na tabela ferias
async function calcularSaldo(
  ctx: QueryCtx,
  funcionarioId: Id<"funcionarios">,
  anoReferencia: number,
  feriasIdExcluir?: Id<"ferias"> // ID do período a excluir do cálculo (para ajustes)
): Promise<{
  diasDireito: number;
  diasUsados: number;
  diasPendentes: number;
  diasDisponiveis: number;
  diasAbono: number;
  dataInicio: string;
  dataFim: string;
  status: "ativo" | "vencido" | "concluido";
} | null> {
  const funcionario = await ctx.db.get(funcionarioId);
  if (!funcionario || !funcionario.admissaoData) return null;

  const regime = funcionario.regimeTrabalho || "clt";
  const config = REGIMES_CONFIG[regime];

  // Calcular anos desde admissão
  const dataAdmissao = new Date(funcionario.admissaoData);
  const anosDesdeAdmissao = anoReferencia - dataAdmissao.getFullYear();

  if (anosDesdeAdmissao < 1) return null; // Ainda não tem direito

  const dataInicio = calcularDataFimPeriodo(
    funcionario.admissaoData,
    anosDesdeAdmissao - 1
  );
  const dataFim = calcularDataFimPeriodo(
    funcionario.admissaoData,
    anosDesdeAdmissao
  );

  // Buscar todos os registros de férias para este funcionário e ano
  const todasFerias = await ctx.db
    .query("ferias")
    .withIndex("by_funcionario_and_ano", (q) =>
      q.eq("funcionarioId", funcionarioId).eq("anoReferencia", anoReferencia)
    )
    .collect();

  // Filtrar períodos a excluir (para ajustes)
  const feriasFiltradas = feriasIdExcluir
    ? todasFerias.filter((f) => f._id !== feriasIdExcluir)
    : todasFerias;

  // Calcular dias usados (aprovado, data_ajustada_aprovada, EmFérias)
  const diasUsados = feriasFiltradas
    .filter(
      (f) =>
        f.status === "aprovado" ||
        f.status === "data_ajustada_aprovada" ||
        f.status === "EmFérias"
    )
    .reduce((acc, f) => acc + f.diasFerias, 0);

  // Calcular dias pendentes (aguardando_aprovacao)
  const diasPendentes = feriasFiltradas
    .filter((f) => f.status === "aguardando_aprovacao")
    .reduce((acc, f) => acc + f.diasFerias, 0);

  // Calcular dias de abono
  const diasAbono = feriasFiltradas.reduce((acc, f) => acc + f.diasAbono, 0);

  // Calcular dias disponíveis
  const diasDireito = 30;
  const diasDisponiveis = diasDireito - diasUsados - diasPendentes - diasAbono;

  // Determinar status do período
  const hoje = new Date();
  const dataFimPeriodo = new Date(dataFim);
  let status: "ativo" | "vencido" | "concluido";
  
  if (diasDireito - diasUsados - diasAbono <= 0) {
    status = "concluido";
  } else if (hoje > dataFimPeriodo) {
    status = "vencido";
  } else {
    status = "ativo";
  }

  return {
    diasDireito,
    diasUsados,
    diasPendentes,
    diasDisponiveis,
    diasAbono,
    dataInicio,
    dataFim,
    status,
  };
}

/**
 * Query: Obter saldo de férias de um funcionário para um ano específico
 */
export const obterSaldo = query({
  args: {
    funcionarioId: v.id("funcionarios"),
    anoReferencia: v.number(),
  },
  returns: v.union(
    v.object({
      anoReferencia: v.number(),
      diasDireito: v.number(),
      diasUsados: v.number(),
      diasPendentes: v.number(),
      diasDisponiveis: v.number(),
      diasAbono: v.number(),
      abonoPermitido: v.boolean(),
      status: v.union(v.literal("ativo"), v.literal("vencido"), v.literal("concluido")),
      dataInicio: v.string(),
      dataFim: v.string(),
      regimeTrabalho: v.string(),
    }),
    v.null()
  ),
  handler: async (ctx, args) => {
    const saldo = await calcularSaldo(ctx, args.funcionarioId, args.anoReferencia);
    if (!saldo) return null;

    const funcionario = await ctx.db.get(args.funcionarioId);
    const regime = funcionario?.regimeTrabalho || "clt";
    const config = REGIMES_CONFIG[regime];

    return {
      anoReferencia: args.anoReferencia,
      ...saldo,
      abonoPermitido: config.abonoPermitido,
      regimeTrabalho: config.nome,
    };
  },
});

/**
 * Query: Listar todos os saldos de um funcionário
 */
export const listarSaldos = query({
  args: {
    funcionarioId: v.id("funcionarios"),
  },
  returns: v.array(
    v.object({
      anoReferencia: v.number(),
      diasDireito: v.number(),
      diasUsados: v.number(),
      diasPendentes: v.number(),
      diasDisponiveis: v.number(),
      diasAbono: v.number(),
      abonoPermitido: v.boolean(),
      status: v.union(v.literal("ativo"), v.literal("vencido"), v.literal("concluido")),
      dataInicio: v.string(),
      dataFim: v.string(),
    })
  ),
  handler: async (ctx, args) => {
    const funcionario = await ctx.db.get(args.funcionarioId);
    if (!funcionario || !funcionario.admissaoData) return [];

    const regime = funcionario.regimeTrabalho || "clt";
    const config = REGIMES_CONFIG[regime];

    const dataAdmissao = new Date(funcionario.admissaoData);
    const anoAtual = new Date().getFullYear();
    const anosDesdeAdmissao = anoAtual - dataAdmissao.getFullYear();

    const saldos = [];

    // Calcular saldos para os últimos 3 anos (atual, anterior e anterior ao anterior)
    for (let i = 0; i < 3; i++) {
      const ano = anoAtual - i;
      const anosPeriodo = ano - dataAdmissao.getFullYear();

      if (anosPeriodo < 1) continue;

      const saldo = await calcularSaldo(ctx, args.funcionarioId, ano);
      if (saldo) {
        saldos.push({
          anoReferencia: ano,
          ...saldo,
          abonoPermitido: config.abonoPermitido,
        });
      }
    }

    return saldos;
  },
});

/**
 * Query: Validar solicitação de férias (regras CLT ou Servidor Público PE)
 */
export const validarSolicitacao = query({
  args: {
    funcionarioId: v.id("funcionarios"),
    anoReferencia: v.number(),
    periodos: v.array(
      v.object({
        dataInicio: v.string(),
        dataFim: v.string(),
      })
    ),
    feriasIdExcluir: v.optional(v.id("ferias")), // ID do período a excluir do cálculo de saldo (para ajustes)
  },
  returns: v.object({
    valido: v.boolean(),
    erros: v.array(v.string()),
    avisos: v.array(v.string()),
    totalDias: v.number(),
    regimeTrabalho: v.string(),
  }),
  handler: async (ctx, args) => {
    const erros: string[] = [];
    const avisos: string[] = [];
    let totalDias = 0;

    // Obter regime de trabalho
    const regime = await obterRegimeTrabalho(ctx, args.funcionarioId);
    const config = REGIMES_CONFIG[regime];

    // Validação 1: Número de períodos
    if (args.periodos.length === 0) {
      erros.push("É necessário adicionar pelo menos 1 período de férias");
    }

    if (args.periodos.length > config.maxPeriodos) {
      erros.push(
        `Máximo de ${config.maxPeriodos} períodos permitidos para ${config.nome}`
      );
    }

    // Calcular dias de cada período e validar
    const diasPorPeriodo: number[] = [];
    for (const periodo of args.periodos) {
      const dias = calcularDiasEntreDatas(periodo.dataInicio, periodo.dataFim);
      diasPorPeriodo.push(dias);
      totalDias += dias;

      // Validação 2: Mínimo de dias por período
      if (dias < config.minDiasPeriodo) {
        erros.push(
          `Período de ${dias} dias é inválido. Mínimo: ${config.minDiasPeriodo} dias corridos (${config.nome})`
        );
      }

      // Validação específica para regime estatutário PE e Municipal
      if ((regime === "estatutario_pe" || regime === "estatutario_municipal") && 'periodosPermitidos' in config) {
        if (!config.periodosPermitidos.includes(dias)) {
          erros.push(
            `Para ${config.nome}, os períodos devem ter exatamente 15 ou 30 dias. Período de ${dias} dias não é permitido.`
          );
        }
      }
    }

    // Validação específica para regime estatutário PE e Municipal
    // Permite períodos fracionados: cada período deve ser 15 ou 30 dias
    // Total não pode exceder 30 dias, mas pode ser menos (períodos fracionados)
    if ((regime === "estatutario_pe" || regime === "estatutario_municipal")) {
      // Verificar se cada período individual é válido (15 ou 30 dias)
      for (const dias of diasPorPeriodo) {
        if (dias !== 15 && dias !== 30) {
          erros.push(
            `Para ${config.nome}, cada período deve ter exatamente 15 ou 30 dias. Período de ${dias} dias não é permitido.`
          );
        }
      }

      // Total não pode exceder 30 dias
      if (totalDias > 30) {
        erros.push(
          `Para ${config.nome}, o total de dias não pode exceder 30 dias. Total solicitado: ${totalDias} dias.`
        );
      }

      // Máximo de 2 períodos
      if (args.periodos.length > 2) {
        erros.push(
          `Para ${config.nome}, o máximo de períodos permitidos é 2.`
        );
      }
    }

    // Validação 3: CLT requer um período com 14+ dias se dividir
    if (regime === "clt" && args.periodos.length > 1 && config.minDiasPeriodoPrincipal) {
      const temPeriodo14Dias = diasPorPeriodo.some((d) => d >= config.minDiasPeriodoPrincipal!);
      if (!temPeriodo14Dias) {
        erros.push(
          `Ao dividir férias em CLT, um período deve ter no mínimo ${config.minDiasPeriodoPrincipal} dias corridos`
        );
      }
    }

    // Validação 4: Verificar saldo disponível (calculado dinamicamente)
    // Se for um ajuste (feriasIdExcluir fornecido), excluir esse período do cálculo
    const saldo = await calcularSaldo(ctx, args.funcionarioId, args.anoReferencia, args.feriasIdExcluir);

    if (!saldo) {
      erros.push(`Você ainda não tem direito a férias referentes ao ano ${args.anoReferencia}`);
    } else {
      // Verificar saldo disponível (já excluindo o período original se for ajuste)
      if (totalDias > saldo.diasDisponiveis) {
        erros.push(
          `Total solicitado (${totalDias} dias) excede saldo disponível (${saldo.diasDisponiveis} dias)`
        );
      }

      // Aviso: Saldo baixo
      if (saldo.diasDisponiveis < 15 && saldo.diasDisponiveis > totalDias) {
        avisos.push(
          `Após essa solicitação, restará ${saldo.diasDisponiveis - totalDias} dias de ${args.anoReferencia}`
        );
      }

      // Aviso: Férias vencendo
      const hoje = new Date();
      const dataFim = new Date(saldo.dataFim);
      const diasAteVencer = Math.ceil((dataFim.getTime() - hoje.getTime()) / (1000 * 60 * 60 * 24));
      if (diasAteVencer < 90 && diasAteVencer > 0) {
        avisos.push(
          `⚠️ Atenção: Seu período aquisitivo ${args.anoReferencia} vence em ${diasAteVencer} dias!`
        );
      }

      if (diasAteVencer < 0) {
        avisos.push(
          `⚠️ URGENTE: Seu período aquisitivo ${args.anoReferencia} está VENCIDO há ${Math.abs(diasAteVencer)} dias!`
        );
      }
    }

    // Validação 5: Verificar conflitos de datas (sobreposição)
    for (let i = 0; i < args.periodos.length; i++) {
      for (let j = i + 1; j < args.periodos.length; j++) {
        const inicio1 = new Date(args.periodos[i].dataInicio);
        const fim1 = new Date(args.periodos[i].dataFim);
        const inicio2 = new Date(args.periodos[j].dataInicio);
        const fim2 = new Date(args.periodos[j].dataFim);

        if (
          (inicio1 <= fim2 && fim1 >= inicio2) ||
          (inicio2 <= fim1 && fim2 >= inicio1)
        ) {
          erros.push("Os períodos não podem se sobrepor");
        }
      }
    }

    // Validação 6: Datas no futuro (aviso)
    const hoje = new Date();
    hoje.setHours(0, 0, 0, 0);
    for (const periodo of args.periodos) {
      const inicio = new Date(periodo.dataInicio);
      if (inicio < hoje) {
        avisos.push("⚠️ Período(s) com data de início no passado ou hoje");
        break;
      }
    }

    // Validação 7: Servidor PE - aviso sobre período preferencial para docentes
    if (regime === "estatutario_pe" || regime === "estatutario_municipal") {
      for (const periodo of args.periodos) {
        const mes = new Date(periodo.dataInicio).getMonth() + 1;
        if (mes === 12 || mes === 1) {
          avisos.push("📅 Período preferencial para docentes (20/12 a 10/01)");
          break;
        }
      }
    }

    return {
      valido: erros.length === 0,
      erros,
      avisos,
      totalDias,
      regimeTrabalho: config.nome,
    };
  },
});
