import { v } from "convex/values";
import { query, mutation, internalMutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { Id } from "./_generated/dataModel";

/**
 * 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
 */

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: 10,
    minDiasPeriodoPrincipal: null, // Não há essa regra
    abonoPermitido: false,
    maxDiasAbono: 0,
  },
  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: 3,
    minDiasPeriodo: 10,
    minDiasPeriodoPrincipal: null,
    abonoPermitido: false,
    maxDiasAbono: 0,
  },
};

// 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: any, funcionarioId: Id<"funcionarios">): Promise<RegimeTrabalho> {
  const funcionario = await ctx.db.get(funcionarioId);
  return funcionario?.regimeTrabalho || "clt"; // Default CLT
}

/**
 * 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) => {
    // Buscar período aquisitivo
    const periodo = await ctx.db
      .query("periodosAquisitivos")
      .withIndex("by_funcionario_and_ano", (q) =>
        q.eq("funcionarioId", args.funcionarioId).eq("anoReferencia", args.anoReferencia)
      )
      .first();

    if (!periodo) {
      // Se não existe, criar automaticamente
      const funcionario = await ctx.db.get(args.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 = args.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);

      // Criar período aquisitivo
      await ctx.db.insert("periodosAquisitivos", {
        funcionarioId: args.funcionarioId,
        anoReferencia: args.anoReferencia,
        dataInicio,
        dataFim,
        diasDireito: 30,
        diasUsados: 0,
        diasPendentes: 0,
        diasDisponiveis: 30,
        abonoPermitido: config.abonoPermitido,
        diasAbono: 0,
        status: "ativo",
      });

      return {
        anoReferencia: args.anoReferencia,
        diasDireito: 30,
        diasUsados: 0,
        diasPendentes: 0,
        diasDisponiveis: 30,
        diasAbono: 0,
        abonoPermitido: config.abonoPermitido,
        status: "ativo" as const,
        dataInicio,
        dataFim,
        regimeTrabalho: config.nome,
      };
    }

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

    return {
      anoReferencia: periodo.anoReferencia,
      diasDireito: periodo.diasDireito,
      diasUsados: periodo.diasUsados,
      diasPendentes: periodo.diasPendentes,
      diasDisponiveis: periodo.diasDisponiveis,
      diasAbono: periodo.diasAbono,
      abonoPermitido: config.abonoPermitido,
      status: periodo.status,
      dataInicio: periodo.dataInicio,
      dataFim: periodo.dataFim,
      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({
      _id: v.id("periodosAquisitivos"),
      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 periodos = await ctx.db
      .query("periodosAquisitivos")
      .withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId))
      .collect();

    return periodos.map((p) => ({
      _id: p._id,
      anoReferencia: p.anoReferencia,
      diasDireito: p.diasDireito,
      diasUsados: p.diasUsados,
      diasPendentes: p.diasPendentes,
      diasDisponiveis: p.diasDisponiveis,
      diasAbono: p.diasAbono,
      abonoPermitido: p.abonoPermitido,
      status: p.status,
      dataInicio: p.dataInicio,
      dataFim: p.dataFim,
    }));
  },
});

/**
 * 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(),
      })
    ),
  },
  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 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
    const periodo = await ctx.db
      .query("periodosAquisitivos")
      .withIndex("by_funcionario_and_ano", (q) =>
        q.eq("funcionarioId", args.funcionarioId).eq("anoReferencia", args.anoReferencia)
      )
      .first();

    if (!periodo) {
      erros.push(`Você ainda não tem direito a férias referentes ao ano ${args.anoReferencia}`);
    } else {
      if (totalDias > periodo.diasDisponiveis) {
        erros.push(
          `Total solicitado (${totalDias} dias) excede saldo disponível (${periodo.diasDisponiveis} dias)`
        );
      }

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

      // Aviso: Férias vencendo
      const hoje = new Date();
      const dataFim = new Date(periodo.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 ${periodo.anoReferencia} vence em ${diasAteVencer} dias!`
        );
      }

      if (diasAteVencer < 0) {
        avisos.push(
          `⚠️ URGENTE: Seu período aquisitivo ${periodo.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") {
      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,
    };
  },
});

/**
 * Internal Mutation: Atualizar saldo após aprovação de férias
 */
export const atualizarSaldoAposAprovacao = internalMutation({
  args: {
    solicitacaoId: v.id("solicitacoesFerias"),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const solicitacao = await ctx.db.get(args.solicitacaoId);
    if (!solicitacao) return null;

    // Buscar período aquisitivo
    const periodo = await ctx.db
      .query("periodosAquisitivos")
      .withIndex("by_funcionario_and_ano", (q) =>
        q.eq("funcionarioId", solicitacao.funcionarioId).eq("anoReferencia", solicitacao.anoReferencia)
      )
      .first();

    if (!periodo) return null;

    // Calcular total de dias
    let totalDias = 0;
    for (const p of solicitacao.periodos) {
      totalDias += p.diasCorridos;
    }

    // Atualizar saldo
    await ctx.db.patch(periodo._id, {
      diasPendentes: periodo.diasPendentes - totalDias,
      diasUsados: periodo.diasUsados + totalDias,
      diasDisponiveis: periodo.diasDireito - (periodo.diasUsados + totalDias) - periodo.diasAbono,
      status: periodo.diasDireito - (periodo.diasUsados + totalDias) <= 0 ? "concluido" : periodo.status,
    });

    return null;
  },
});

/**
 * Internal Mutation: Reservar dias (ao criar solicitação)
 */
export const reservarDias = internalMutation({
  args: {
    funcionarioId: v.id("funcionarios"),
    anoReferencia: v.number(),
    totalDias: v.number(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const periodo = await ctx.db
      .query("periodosAquisitivos")
      .withIndex("by_funcionario_and_ano", (q) =>
        q.eq("funcionarioId", args.funcionarioId).eq("anoReferencia", args.anoReferencia)
      )
      .first();

    if (!periodo) return null;

    await ctx.db.patch(periodo._id, {
      diasPendentes: periodo.diasPendentes + args.totalDias,
      diasDisponiveis: periodo.diasDisponiveis - args.totalDias,
    });

    return null;
  },
});

/**
 * Internal Mutation: Liberar dias (ao reprovar solicitação)
 */
export const liberarDias = internalMutation({
  args: {
    solicitacaoId: v.id("solicitacoesFerias"),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const solicitacao = await ctx.db.get(args.solicitacaoId);
    if (!solicitacao) return null;

    const periodo = await ctx.db
      .query("periodosAquisitivos")
      .withIndex("by_funcionario_and_ano", (q) =>
        q.eq("funcionarioId", solicitacao.funcionarioId).eq("anoReferencia", solicitacao.anoReferencia)
      )
      .first();

    if (!periodo) return null;

    let totalDias = 0;
    for (const p of solicitacao.periodos) {
      totalDias += p.diasCorridos;
    }

    await ctx.db.patch(periodo._id, {
      diasPendentes: periodo.diasPendentes - totalDias,
      diasDisponiveis: periodo.diasDisponiveis + totalDias,
    });

    return null;
  },
});

/**
 * Internal Mutation: Criar períodos aquisitivos para todos os funcionários
 */
export const criarPeriodosAquisitivos = internalMutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    const funcionarios = await ctx.db.query("funcionarios").collect();
    const anoAtual = new Date().getFullYear();

    for (const func of funcionarios) {
      if (!func.admissaoData) continue;

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

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

      // Criar períodos para os últimos 2 anos (atual e anterior)
      for (let i = 0; i < 2; i++) {
        const ano = anoAtual - i;
        const anosPeriodo = ano - dataAdmissao.getFullYear();

        if (anosPeriodo < 1) continue;

        // Verificar se já existe
        const periodoExistente = await ctx.db
          .query("periodosAquisitivos")
          .withIndex("by_funcionario_and_ano", (q) =>
            q.eq("funcionarioId", func._id).eq("anoReferencia", ano)
          )
          .first();

        if (periodoExistente) continue;

        const dataInicio = calcularDataFimPeriodo(func.admissaoData, anosPeriodo - 1);
        const dataFim = calcularDataFimPeriodo(func.admissaoData, anosPeriodo);

        await ctx.db.insert("periodosAquisitivos", {
          funcionarioId: func._id,
          anoReferencia: ano,
          dataInicio,
          dataFim,
          diasDireito: 30,
          diasUsados: 0,
          diasPendentes: 0,
          diasDisponiveis: 30,
          abonoPermitido: config.abonoPermitido,
          diasAbono: 0,
          status: "ativo",
        });
      }
    }

    return null;
  },
});
