import { v } from 'convex/values';
import { mutation, query, internalMutation } from './_generated/server';
import type { MutationCtx, QueryCtx } from './_generated/server';
import { getCurrentUserFunction } from './auth';
import type { Id, Doc } from './_generated/dataModel';
import { validarLocalizacaoGeofencingInternal } from './enderecosMarcacao';
import { internal, api } from './_generated/api';

/**
 * Calcula distância entre duas coordenadas (fórmula de Haversine)
 * Retorna distância em metros
 */
function calcularDistancia(lat1: number, lon1: number, lat2: number, lon2: number): number {
	const R = 6371000; // Raio da Terra em metros
	const dLat = ((lat2 - lat1) * Math.PI) / 180;
	const dLon = ((lon2 - lon1) * Math.PI) / 180;
	const a =
		Math.sin(dLat / 2) * Math.sin(dLat / 2) +
		Math.cos((lat1 * Math.PI) / 180) *
			Math.cos((lat2 * Math.PI) / 180) *
			Math.sin(dLon / 2) *
			Math.sin(dLon / 2);
	const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	return R * c;
}

/**
 * Obtém geolocalização aproximada por IP usando serviço externo
 */
async function obterGeoPorIP(ipAddress: string): Promise<{
	latitude: number;
	longitude: number;
	cidade?: string;
	estado?: string;
	pais?: string;
} | null> {
	try {
		// Usar ipapi.co (gratuito, sem chave para uso limitado)
		const response = await fetch(`https://ipapi.co/${ipAddress}/json/`, {
			headers: {
				'User-Agent': 'SGSE-App/1.0'
			}
		});

		if (response.ok) {
			const data = (await response.json()) as {
				latitude?: number;
				longitude?: number;
				city?: string;
				region?: string;
				country_name?: string;
				error?: boolean;
			};

			if (!data.error && data.latitude && data.longitude) {
				return {
					latitude: data.latitude,
					longitude: data.longitude,
					cidade: data.city,
					estado: data.region,
					pais: data.country_name
				};
			}
		}
	} catch (error) {
		console.warn('Erro ao obter geolocalização por IP:', error);
	}

	return null;
}

/**
 * Valida localização contra IP geolocation e histórico
 * Retorna informações detalhadas para salvar no registro
 */
async function validarLocalizacao(
	ctx: MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	latitude: number,
	longitude: number,
	ipAddress?: string,
	confiabilidadeGPS?: number
): Promise<{
	valida: boolean;
	motivo?: string;
	scoreConfianca: number; // 0-1
	avisos: string[];
	distanciaIPvsGPS?: number; // Distância em metros entre IP geolocation e GPS
	velocidadeUltimoRegistro?: number; // Velocidade calculada em km/h
	distanciaUltimoRegistro?: number; // Distância em metros do último registro
	tempoDecorridoHoras?: number; // Tempo em horas desde último registro
}> {
	const avisos: string[] = [];
	let scoreConfianca = confiabilidadeGPS || 0.5;
	let valida = true;
	let distanciaIPvsGPS: number | undefined = undefined;
	let velocidadeUltimoRegistro: number | undefined = undefined;
	let distanciaUltimoRegistro: number | undefined = undefined;
	let tempoDecorridoHoras: number | undefined = undefined;

	// 1. Validar coordenadas básicas
	if (
		isNaN(latitude) ||
		isNaN(longitude) ||
		latitude < -90 ||
		latitude > 90 ||
		longitude < -180 ||
		longitude > 180
	) {
		return {
			valida: false,
			motivo: 'Coordenadas inválidas',
			scoreConfianca: 0,
			avisos: [],
			distanciaIPvsGPS,
			velocidadeUltimoRegistro,
			distanciaUltimoRegistro,
			tempoDecorridoHoras
		};
	}

	// 2. Comparar com geolocalização do IP
	if (ipAddress) {
		const ipGeo = await obterGeoPorIP(ipAddress);
		if (ipGeo) {
			distanciaIPvsGPS = calcularDistancia(latitude, longitude, ipGeo.latitude, ipGeo.longitude);

			// Se diferença > 50km, muito suspeito
			if (distanciaIPvsGPS > 50000) {
				valida = false;
				scoreConfianca = Math.min(scoreConfianca, 0.2);
				avisos.push(
					`Localização GPS (${latitude.toFixed(6)}, ${longitude.toFixed(6)}) está muito distante da localização do IP (${distanciaIPvsGPS.toFixed(0)}m). Possível falsificação.`
				);
			} else if (distanciaIPvsGPS > 10000) {
				// Se diferença entre 10-50km, suspeito mas aceitável (pode ser VPN/mobile)
				scoreConfianca *= 0.7;
				avisos.push(
					`Localização GPS está a ${distanciaIPvsGPS.toFixed(0)}m da localização do IP. Isso pode ser normal se estiver usando VPN ou dados móveis.`
				);
			} else if (distanciaIPvsGPS < 5000) {
				// Se diferença < 5km, aumenta confiança
				scoreConfianca = Math.min(scoreConfianca + 0.2, 1);
			}
		}
	}

	// 3. Validar histórico de localizações do funcionário
	const ultimosRegistros = await ctx.db
		.query('registrosPonto')
		.withIndex('by_funcionario_data', (q) => q.eq('funcionarioId', funcionarioId))
		.order('desc')
		.take(5);

	if (ultimosRegistros.length > 0) {
		// Verificar movimento impossível
		for (const registro of ultimosRegistros) {
			if (registro.latitude && registro.longitude && registro.timestamp) {
				distanciaUltimoRegistro = calcularDistancia(
					latitude,
					longitude,
					registro.latitude,
					registro.longitude
				);
				const tempoDecorrido = Date.now() - registro.timestamp;
				tempoDecorridoHoras = tempoDecorrido / (1000 * 60 * 60);

				// Calcular velocidade (km/h) se tempo decorrido > 0
				if (tempoDecorridoHoras > 0 && tempoDecorridoHoras < 24) {
					velocidadeUltimoRegistro = distanciaUltimoRegistro / 1000 / tempoDecorridoHoras; // km/h

					// Se velocidade > 1000 km/h, impossível (mais rápido que avião)
					if (velocidadeUltimoRegistro > 1000) {
						valida = false;
						scoreConfianca = 0;
						avisos.push(
							`Movimento impossível detectado: ${velocidadeUltimoRegistro.toFixed(0)} km/h. Localização anterior há ${tempoDecorridoHoras.toFixed(1)}h está a ${(distanciaUltimoRegistro / 1000).toFixed(1)}km.`
						);
						break;
					}

					// Se velocidade > 200 km/h, suspeito (mas possível em avião)
					if (velocidadeUltimoRegistro > 200 && velocidadeUltimoRegistro <= 1000) {
						scoreConfianca *= 0.6;
						avisos.push(
							`Movimento muito rápido: ${velocidadeUltimoRegistro.toFixed(0)} km/h. Pode ser viagem, mas verifique se é legítimo.`
						);
					}
				}
				break; // Usar apenas o último registro
			}
		}
	}

	// 4. Validar confiabilidade GPS do frontend
	if (confiabilidadeGPS !== undefined) {
		if (confiabilidadeGPS < 0.3) {
			scoreConfianca *= 0.5;
			avisos.push(
				`Confiabilidade GPS baixa (${(confiabilidadeGPS * 100).toFixed(0)}%). Localização pode não ser precisa.`
			);
		}
	}

	return {
		valida,
		motivo: avisos.length > 0 ? avisos[0] : undefined,
		scoreConfianca: Math.max(0, Math.min(1, scoreConfianca)),
		avisos,
		distanciaIPvsGPS,
		velocidadeUltimoRegistro,
		distanciaUltimoRegistro,
		tempoDecorridoHoras
	};
}

/**
 * Valida dados de acelerômetro para detectar autenticidade do registro
 * Retorna informações de validação sem bloquear o registro
 */
function validarAcelerometro(
	isDesktop: boolean | undefined,
	sensorDisponivel: boolean | undefined,
	permissaoSensorNegada: boolean | undefined,
	acelerometroX: number | undefined,
	acelerometroY: number | undefined,
	acelerometroZ: number | undefined,
	movimentoDetectado: boolean | undefined,
	magnitudeMovimento: number | undefined,
	variacaoAcelerometro: number | undefined
): {
	valida: boolean;
	motivo?: string;
	scoreConfianca: number; // 0-1
	avisos: string[];
} {
	const avisos: string[] = [];
	let scoreConfianca = 1.0;

	// Se for desktop, ausência de sensor não é suspeito
	if (isDesktop === true) {
		if (sensorDisponivel === false || !acelerometroX) {
			// Desktop não tem sensor - isso é normal, não reduzir confiança
			return {
				valida: true,
				scoreConfianca: 1.0,
				avisos: []
			};
		}
	}

	// Se permissão foi negada, apenas reduzir score de confiança (não bloqueia registro)
	if (permissaoSensorNegada === true) {
		scoreConfianca *= 0.9;
		avisos.push('Permissão de sensor negada pelo usuário (não bloqueia registro)');
		// Continuar validação normalmente
	}

	// Se sensor não está disponível e não é desktop, pode ser suspeito (mas não bloqueia)
	if (sensorDisponivel === false && isDesktop !== true) {
		scoreConfianca *= 0.8;
		avisos.push('Sensor de movimento não disponível no dispositivo móvel');
	}

	// Se sensor está disponível mas não há dados, pode ser suspeito
	if (sensorDisponivel === true && (!acelerometroX || !acelerometroY || !acelerometroZ)) {
		scoreConfianca *= 0.7;
		avisos.push('Sensor disponível mas dados de acelerômetro não coletados');
	}

	// Se há dados de acelerômetro, validar
	if (acelerometroX !== undefined && acelerometroY !== undefined && acelerometroZ !== undefined) {
		// Verificar se valores são realistas (aceleração geralmente entre -20 e +20 m/s² em uso normal)
		const magnitude =
			magnitudeMovimento ||
			Math.sqrt(
				acelerometroX * acelerometroX +
					acelerometroY * acelerometroY +
					acelerometroZ * acelerometroZ
			);

		if (magnitude > 50) {
			// Aceleração muito alta pode indicar leitura errada ou emulador
			scoreConfianca *= 0.6;
			avisos.push(
				`Magnitude de movimento muito alta (${magnitude.toFixed(2)} m/s²). Pode indicar leitura incorreta.`
			);
		}

		// Se não há movimento detectado quando deveria haver (em móvel), pode ser suspeito
		if (
			isDesktop !== true &&
			movimentoDetectado === false &&
			variacaoAcelerometro !== undefined &&
			variacaoAcelerometro < 0.001
		) {
			// Variância muito baixa pode indicar que o dispositivo está parado ou emulador
			scoreConfianca *= 0.9;
			avisos.push(
				'Nenhum movimento detectado durante o registro. Pode ser normal se o dispositivo estava parado.'
			);
		}

		// Se há movimento, aumenta confiança
		if (movimentoDetectado === true) {
			scoreConfianca = Math.min(scoreConfianca * 1.1, 1.0);
		}
	}

	return {
		valida: true, // Sempre retorna true - não bloqueia registro, apenas informa através do score
		motivo: avisos.length > 0 ? avisos[0] : undefined,
		scoreConfianca: Math.max(0, Math.min(1, scoreConfianca)),
		avisos
	};
}

/**
 * Gera URL para upload de imagem do ponto
 */
export const generateUploadUrl = mutation({
	args: {},
	handler: async (ctx) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}
		return await ctx.storage.generateUploadUrl();
	}
});

/**
 * Calcula se o registro está dentro do prazo baseado na configuração
 * Se toleranciaMinutos for 0, desconsidera atrasos (sempre retorna true)
 */
function calcularStatusPonto(
	hora: number,
	minuto: number,
	horarioConfigurado: string,
	toleranciaMinutos: number
): boolean {
	// Se tolerância for 0, desconsiderar atrasos (qualquer registro é válido)
	if (toleranciaMinutos === 0) {
		return true;
	}

	const [horaConfig, minutoConfig] = horarioConfigurado.split(':').map(Number);
	const totalMinutosRegistro = hora * 60 + minuto;
	const totalMinutosConfigurado = horaConfig * 60 + minutoConfig;
	const diferenca = totalMinutosRegistro - totalMinutosConfigurado;
	return diferenca <= toleranciaMinutos && diferenca >= -toleranciaMinutos;
}

/**
 * Valida sequência de registros antes de permitir novo registro
 * Retorna erro se a sequência não for válida
 */
async function validarSequenciaRegistro(
	ctx: QueryCtx | MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string,
	tipoEsperado: 'entrada' | 'saida_almoco' | 'retorno_almoco' | 'saida'
): Promise<{ valido: boolean; motivo?: string }> {
	const registrosHoje = await ctx.db
		.query('registrosPonto')
		.withIndex('by_funcionario_data', (q) => q.eq('funcionarioId', funcionarioId).eq('data', data))
		.order('desc')
		.collect();

	// Se não há registros, só pode ser entrada
	if (registrosHoje.length === 0) {
		if (tipoEsperado !== 'entrada') {
			return {
				valido: false,
				motivo: 'Primeiro registro do dia deve ser uma entrada'
			};
		}
		return { valido: true };
	}

	const ultimoRegistro = registrosHoje[0];

	// Validar sequência lógica
	switch (tipoEsperado) {
		case 'entrada':
			// Só pode registrar entrada se o último foi saída (novo dia) ou não há registros
			if (ultimoRegistro.tipo !== 'saida') {
				return {
					valido: false,
					motivo: `Não é possível registrar entrada. Último registro foi: ${ultimoRegistro.tipo}. Esperado: saída do dia anterior.`
				};
			}
			break;

		case 'saida_almoco':
			// Só pode registrar saída almoço se o último foi entrada
			if (ultimoRegistro.tipo !== 'entrada') {
				return {
					valido: false,
					motivo: `Não é possível registrar saída para almoço. Último registro foi: ${ultimoRegistro.tipo}. Deve registrar entrada primeiro.`
				};
			}
			break;

		case 'retorno_almoco':
			// Só pode registrar retorno almoço se o último foi saída almoço
			if (ultimoRegistro.tipo !== 'saida_almoco') {
				return {
					valido: false,
					motivo: `Não é possível registrar retorno do almoço. Último registro foi: ${ultimoRegistro.tipo}. Deve registrar saída para almoço primeiro.`
				};
			}
			break;

		case 'saida':
			// Só pode registrar saída se o último foi retorno almoço ou entrada (sem intervalo)
			if (ultimoRegistro.tipo !== 'retorno_almoco' && ultimoRegistro.tipo !== 'entrada') {
				return {
					valido: false,
					motivo: `Não é possível registrar saída. Último registro foi: ${ultimoRegistro.tipo}. Deve registrar retorno do almoço ou ter apenas entrada.`
				};
			}
			// Se último foi entrada, verificar se não há saída almoço pendente
			if (ultimoRegistro.tipo === 'entrada') {
				const temSaidaAlmoco = registrosHoje.some((r) => r.tipo === 'saida_almoco');
				if (temSaidaAlmoco) {
					return {
						valido: false,
						motivo: 'Não é possível registrar saída. Há saída para almoço registrada, mas falta o retorno do almoço.'
					};
				}
			}
			break;
	}

	return { valido: true };
}

/**
 * Determina o tipo de registro baseado na sequência lógica
 */
async function determinarTipoRegistro(
	ctx: QueryCtx | MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string
): Promise<'entrada' | 'saida_almoco' | 'retorno_almoco' | 'saida'> {
	const registrosHoje = await ctx.db
		.query('registrosPonto')
		.withIndex('by_funcionario_data', (q) => q.eq('funcionarioId', funcionarioId).eq('data', data))
		.order('desc')
		.collect();

	if (registrosHoje.length === 0) {
		return 'entrada';
	}

	const ultimoRegistro = registrosHoje[0];
	switch (ultimoRegistro.tipo) {
		case 'entrada':
			return 'saida_almoco';
		case 'saida_almoco':
			return 'retorno_almoco';
		case 'retorno_almoco':
			return 'saida';
		case 'saida':
			// Se já saiu, próximo registro é entrada (novo dia)
			return 'entrada';
		default:
			return 'entrada';
	}
}

/**
 * Registra um ponto (entrada, saída, etc.)
 */
export const registrarPonto = mutation({
	args: {
		imagemId: v.optional(v.id('_storage')),
		informacoesDispositivo: v.optional(
			v.object({
				ipAddress: v.optional(v.string()),
				ipPublico: v.optional(v.string()),
				ipLocal: v.optional(v.string()),
				userAgent: v.optional(v.string()),
				browser: v.optional(v.string()),
				browserVersion: v.optional(v.string()),
				engine: v.optional(v.string()),
				sistemaOperacional: v.optional(v.string()),
				osVersion: v.optional(v.string()),
				arquitetura: v.optional(v.string()),
				plataforma: v.optional(v.string()),
				latitude: v.optional(v.number()),
				longitude: v.optional(v.number()),
				precisao: v.optional(v.number()),
				altitude: v.optional(v.union(v.number(), v.null())),
				altitudeAccuracy: v.optional(v.union(v.number(), v.null())),
				heading: v.optional(v.union(v.number(), v.null())),
				speed: v.optional(v.union(v.number(), v.null())),
				confiabilidadeGPS: v.optional(v.number()),
				suspeitaSpoofing: v.optional(v.boolean()),
				motivoSuspeita: v.optional(v.string()),
				endereco: v.optional(v.string()),
				cidade: v.optional(v.string()),
				estado: v.optional(v.string()),
				pais: v.optional(v.string()),
				timezone: v.optional(v.string()),
				deviceType: v.optional(v.string()),
				deviceModel: v.optional(v.string()),
				screenResolution: v.optional(v.string()),
				coresTela: v.optional(v.number()),
				idioma: v.optional(v.string()),
				isMobile: v.optional(v.boolean()),
				isTablet: v.optional(v.boolean()),
				isDesktop: v.optional(v.boolean()),
				connectionType: v.optional(v.string()),
				memoryInfo: v.optional(v.string()),
				// Campos de sensores (acelerômetro e giroscópio)
				sensorDisponivel: v.optional(v.boolean()),
				permissaoNegada: v.optional(v.boolean()),
				acelerometro: v.optional(
					v.object({
						x: v.number(),
						y: v.number(),
						z: v.number(),
						movimentoDetectado: v.boolean(),
						magnitude: v.number(),
						variacao: v.number(),
						timestamp: v.number()
					})
				),
				giroscopio: v.optional(
					v.object({
						alpha: v.number(),
						beta: v.number(),
						gamma: v.number()
					})
				)
			})
		),
		timestamp: v.number(),
		sincronizadoComServidor: v.boolean(),
		justificativa: v.optional(v.string())
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		if (!usuario.funcionarioId) {
			throw new Error('Usuário não possui funcionário associado');
		}

		// Verificar se funcionário está ativo
		const funcionario = await ctx.db.get(usuario.funcionarioId);
		if (!funcionario) {
			throw new Error('Funcionário não encontrado');
		}

		// Obter configuração de ponto
		const config = await ctx.db
			.query('configuracaoPonto')
			.withIndex('by_ativo', (q) => q.eq('ativo', true))
			.first();

		if (!config) {
			throw new Error('Configuração de ponto não encontrada');
		}

		// Converter timestamp para data/hora
		// O timestamp pode vir ajustado com GMT offset do frontend (se GMT !== 0)
		// ou em UTC puro (se GMT === 0). Usamos UTC methods para extrair os valores
		// diretamente do timestamp recebido, seja ele ajustado ou não
		const dataObj = new Date(args.timestamp);
		// Usar UTC methods porque:
		// - Se GMT === 0: timestamp está em UTC puro, métodos UTC extraem corretamente
		// - Se GMT !== 0: timestamp já vem ajustado do frontend, métodos UTC extraem o horário ajustado
		const hora = dataObj.getUTCHours();
		const minuto = dataObj.getUTCMinutes();
		const segundo = dataObj.getUTCSeconds();

		// Obter data no formato YYYY-MM-DD usando UTC
		const ano = dataObj.getUTCFullYear();
		const mes = String(dataObj.getUTCMonth() + 1).padStart(2, '0');
		const dia = String(dataObj.getUTCDate()).padStart(2, '0');
		const data = `${ano}-${mes}-${dia}`;

		// Verificar se já existe registro no mesmo minuto
		const funcionarioId = usuario.funcionarioId; // Já verificado acima, não é undefined
		const registrosMinuto = await ctx.db
			.query('registrosPonto')
			.withIndex('by_funcionario_data', (q) =>
				q.eq('funcionarioId', funcionarioId).eq('data', data)
			)
			.collect();

		const registroDuplicado = registrosMinuto.find((r) => r.hora === hora && r.minuto === minuto);

		if (registroDuplicado) {
			throw new Error('Já existe um registro neste minuto');
		}

		// Verificar se funcionário está dispensado de registrar ponto
		const dispensas = await ctx.db
			.query('dispensasRegistro')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
			.filter((q) => q.eq(q.field('ativo'), true))
			.collect();

		const dataConsulta = new Date(data);
		for (const dispensa of dispensas) {
			// Se for isento, sempre está dispensado
			if (dispensa.isento) {
				throw new Error('Registro dispensado pelo gestor: Isento de registro (caso excepcional)');
			}

			// Verificar se está no período
			const dataInicio = new Date(dispensa.dataInicio);
			const dataFim = new Date(dispensa.dataFim);

			if (dataConsulta >= dataInicio && dataConsulta <= dataFim) {
				// Verificar hora e minuto se necessário
				const timestampConsulta = new Date(
					`${data}T${hora.toString().padStart(2, '0')}:${minuto.toString().padStart(2, '0')}:00`
				).getTime();
				const timestampInicio = new Date(
					`${dispensa.dataInicio}T${dispensa.horaInicio.toString().padStart(2, '0')}:${dispensa.minutoInicio.toString().padStart(2, '0')}:00`
				).getTime();
				const timestampFim = new Date(
					`${dispensa.dataFim}T${dispensa.horaFim.toString().padStart(2, '0')}:${dispensa.minutoFim.toString().padStart(2, '0')}:00`
				).getTime();

				if (timestampConsulta >= timestampInicio && timestampConsulta <= timestampFim) {
					throw new Error(`Registro dispensado pelo gestor: ${dispensa.motivo}`);
				}
			}

			// Verificar se expirou (desativar na mutation de registro)
			const agora = new Date();
			const dataFimTimestamp = new Date(
				`${dispensa.dataFim}T${dispensa.horaFim.toString().padStart(2, '0')}:${dispensa.minutoFim.toString().padStart(2, '0')}:00`
			).getTime();

			if (agora.getTime() > dataFimTimestamp && !dispensa.isento) {
				// Desativar dispensa expirada (mutation pode fazer isso)
				await ctx.db.patch(dispensa._id, {
					ativo: false
				});
			}
		}

		// Determinar tipo de registro
		const tipo = await determinarTipoRegistro(ctx, usuario.funcionarioId, data);

		// Validar sequência de registros
		const validacaoSequencia = await validarSequenciaRegistro(
			ctx,
			usuario.funcionarioId,
			data,
			tipo
		);

		if (!validacaoSequencia.valido) {
			throw new Error(validacaoSequencia.motivo || 'Sequência de registros inválida');
		}

		// Calcular horário esperado e tolerância
		let horarioConfigurado = '';
		switch (tipo) {
			case 'entrada':
				horarioConfigurado = config.horarioEntrada;
				break;
			case 'saida_almoco':
				horarioConfigurado = config.horarioSaidaAlmoco;
				break;
			case 'retorno_almoco':
				horarioConfigurado = config.horarioRetornoAlmoco;
				break;
			case 'saida':
				horarioConfigurado = config.horarioSaida;
				break;
		}

		const dentroDoPrazo = calcularStatusPonto(
			hora,
			minuto,
			horarioConfigurado,
			config.toleranciaMinutos
		);

		// Validar localização se fornecida e salvar informações detalhadas
		let validacaoLocalizacao: {
			valida: boolean;
			motivo?: string;
			scoreConfianca: number;
			avisos: string[];
			distanciaIPvsGPS?: number;
			velocidadeUltimoRegistro?: number;
			distanciaUltimoRegistro?: number;
			tempoDecorridoHoras?: number;
		} | null = null;

		if (args.informacoesDispositivo?.latitude && args.informacoesDispositivo?.longitude) {
			validacaoLocalizacao = await validarLocalizacao(
				ctx,
				usuario.funcionarioId,
				args.informacoesDispositivo.latitude,
				args.informacoesDispositivo.longitude,
				args.informacoesDispositivo.ipPublico || args.informacoesDispositivo.ipAddress,
				args.informacoesDispositivo.confiabilidadeGPS
			);

			// Sempre registrar, mesmo com baixa confiabilidade
			// Mas salvar todas as informações detalhadas para análise posterior
			const suspeitaFrontend = args.informacoesDispositivo.suspeitaSpoofing;
			const suspeitaBackend = !validacaoLocalizacao.valida;
			const baixaConfianca = validacaoLocalizacao.scoreConfianca < 0.5;

			if (suspeitaFrontend || suspeitaBackend || baixaConfianca) {
				console.warn(
					'⚠️ LOCALIZAÇÃO COM BAIXA CONFIABILIDADE DETECTADA (registrando normalmente):',
					{
						funcionarioId: usuario.funcionarioId,
						latitude: args.informacoesDispositivo.latitude,
						longitude: args.informacoesDispositivo.longitude,
						confiabilidadeGPSFrontend: args.informacoesDispositivo.confiabilidadeGPS,
						scoreConfiancaBackend: validacaoLocalizacao.scoreConfianca,
						suspeitaFrontend: suspeitaFrontend ? args.informacoesDispositivo.motivoSuspeita : null,
						suspeitaBackend: suspeitaBackend ? validacaoLocalizacao.motivo : null,
						avisos: validacaoLocalizacao.avisos
					}
				);
			}
		}

		// Validar geofencing (localização permitida) se habilitado
		let validacaoGeofencing: {
			dentroRaio: boolean;
			enderecoMaisProximo?: Id<'enderecosMarcacao'>;
			distanciaMetros?: number;
			raioUsado?: number;
			enderecoEncontrado?: string;
			avisos: string[];
		} | null = null;

		if (
			config.validarLocalizacao !== false &&
			args.informacoesDispositivo?.latitude &&
			args.informacoesDispositivo?.longitude
		) {
			const geofencing = await validarLocalizacaoGeofencingInternal(
				ctx,
				usuario.funcionarioId,
				args.informacoesDispositivo.latitude,
				args.informacoesDispositivo.longitude,
				config.toleranciaDistanciaMetros ?? 100
			);

			validacaoGeofencing = geofencing;

			// Adicionar avisos de geofencing aos avisos de validação
			if (geofencing.avisos.length > 0) {
				if (!validacaoLocalizacao) {
					validacaoLocalizacao = {
						valida: true,
						scoreConfianca: 1,
						avisos: []
					};
				}
				validacaoLocalizacao.avisos.push(...geofencing.avisos);

				// Reduzir score de confiança se estiver fora do raio
				if (!geofencing.dentroRaio) {
					validacaoLocalizacao.scoreConfianca = Math.min(validacaoLocalizacao.scoreConfianca, 0.7);
				}
			}
		}

		// Validar dados de acelerômetro (não bloqueia registro - apenas informa)
		const validacaoAcelerometro = validarAcelerometro(
			args.informacoesDispositivo?.isDesktop,
			args.informacoesDispositivo?.sensorDisponivel,
			args.informacoesDispositivo?.permissaoNegada,
			args.informacoesDispositivo?.acelerometro?.x,
			args.informacoesDispositivo?.acelerometro?.y,
			args.informacoesDispositivo?.acelerometro?.z,
			args.informacoesDispositivo?.acelerometro?.movimentoDetectado,
			args.informacoesDispositivo?.acelerometro?.magnitude,
			args.informacoesDispositivo?.acelerometro?.variacao
		);

		// Nota: A validação de acelerômetro não bloqueia o registro - apenas reduz o score de confiança
		// Apenas câmera e localização são obrigatórias para registrar ponto

		// Combinar avisos de validação de localização e acelerômetro
		const todosAvisos = [
			...(validacaoLocalizacao?.avisos || []),
			...(validacaoAcelerometro.avisos || [])
		];

		// Combinar scores de confiança (média ponderada)
		let scoreFinalConfianca = 1.0;
		if (validacaoLocalizacao && validacaoAcelerometro) {
			// GPS tem peso 0.7, acelerômetro tem peso 0.3
			scoreFinalConfianca =
				validacaoLocalizacao.scoreConfianca * 0.7 + validacaoAcelerometro.scoreConfianca * 0.3;
		} else if (validacaoLocalizacao) {
			scoreFinalConfianca = validacaoLocalizacao.scoreConfianca;
		} else if (validacaoAcelerometro) {
			scoreFinalConfianca = validacaoAcelerometro.scoreConfianca;
		}

		// Criar registro
		const registroId = await ctx.db.insert('registrosPonto', {
			funcionarioId: usuario.funcionarioId,
			tipo,
			data,
			hora,
			minuto,
			segundo,
			timestamp: args.timestamp,
			imagemId: args.imagemId,
			sincronizadoComServidor: args.sincronizadoComServidor,
			toleranciaMinutos: config.toleranciaMinutos,
			dentroDoPrazo,
			justificativa: args.justificativa,
			ipAddress: args.informacoesDispositivo?.ipAddress,
			ipPublico: args.informacoesDispositivo?.ipPublico,
			ipLocal: args.informacoesDispositivo?.ipLocal,
			userAgent: args.informacoesDispositivo?.userAgent,
			browser: args.informacoesDispositivo?.browser,
			browserVersion: args.informacoesDispositivo?.browserVersion,
			engine: args.informacoesDispositivo?.engine,
			sistemaOperacional: args.informacoesDispositivo?.sistemaOperacional,
			osVersion: args.informacoesDispositivo?.osVersion,
			arquitetura: args.informacoesDispositivo?.arquitetura,
			plataforma: args.informacoesDispositivo?.plataforma,
			latitude: args.informacoesDispositivo?.latitude,
			longitude: args.informacoesDispositivo?.longitude,
			precisao: args.informacoesDispositivo?.precisao,
			altitude: args.informacoesDispositivo?.altitude,
			altitudeAccuracy: args.informacoesDispositivo?.altitudeAccuracy,
			heading: args.informacoesDispositivo?.heading,
			speed: args.informacoesDispositivo?.speed,
			confiabilidadeGPS: args.informacoesDispositivo?.confiabilidadeGPS,
			scoreConfiancaBackend: scoreFinalConfianca,
			suspeitaSpoofing:
				args.informacoesDispositivo?.suspeitaSpoofing ||
				(validacaoLocalizacao
					? validacaoLocalizacao.scoreConfianca < 0.5 || !validacaoLocalizacao.valida
					: undefined) ||
				(validacaoAcelerometro
					? validacaoAcelerometro.scoreConfianca < 0.5 || !validacaoAcelerometro.valida
					: undefined),
			motivoSuspeita:
				args.informacoesDispositivo?.motivoSuspeita ||
				validacaoLocalizacao?.motivo ||
				validacaoAcelerometro?.motivo ||
				(todosAvisos.length > 0 ? todosAvisos.join('; ') : undefined),
			// Informações detalhadas de validação (sempre salvar quando houver validação)
			avisosValidacao: todosAvisos.length > 0 ? todosAvisos : undefined,
			// Informações de Geofencing
			enderecoMarcacaoEsperado: validacaoGeofencing?.enderecoMaisProximo,
			distanciaEnderecoEsperado: validacaoGeofencing?.distanciaMetros,
			dentroRaioPermitido: validacaoGeofencing?.dentroRaio,
			enderecoMarcacaoUsado: validacaoGeofencing?.enderecoMaisProximo,
			raioToleranciaUsado: validacaoGeofencing?.raioUsado,
			endereco: args.informacoesDispositivo?.endereco,
			cidade: args.informacoesDispositivo?.cidade,
			estado: args.informacoesDispositivo?.estado,
			pais: args.informacoesDispositivo?.pais,
			timezone: args.informacoesDispositivo?.timezone,
			deviceType: args.informacoesDispositivo?.deviceType,
			deviceModel: args.informacoesDispositivo?.deviceModel,
			screenResolution: args.informacoesDispositivo?.screenResolution,
			coresTela: args.informacoesDispositivo?.coresTela,
			idioma: args.informacoesDispositivo?.idioma,
			isMobile: args.informacoesDispositivo?.isMobile,
			isTablet: args.informacoesDispositivo?.isTablet,
			isDesktop: args.informacoesDispositivo?.isDesktop,
			connectionType: args.informacoesDispositivo?.connectionType,
			memoryInfo: args.informacoesDispositivo?.memoryInfo,
			// Dados de sensores (Acelerômetro e Giroscópio)
			acelerometroX: args.informacoesDispositivo?.acelerometro?.x,
			acelerometroY: args.informacoesDispositivo?.acelerometro?.y,
			acelerometroZ: args.informacoesDispositivo?.acelerometro?.z,
			movimentoDetectado: args.informacoesDispositivo?.acelerometro?.movimentoDetectado,
			magnitudeMovimento: args.informacoesDispositivo?.acelerometro?.magnitude,
			variacaoAcelerometro: args.informacoesDispositivo?.acelerometro?.variacao,
			giroscopioAlpha: args.informacoesDispositivo?.giroscopio?.alpha,
			giroscopioBeta: args.informacoesDispositivo?.giroscopio?.beta,
			giroscopioGamma: args.informacoesDispositivo?.giroscopio?.gamma,
			sensorDisponivel: args.informacoesDispositivo?.sensorDisponivel,
			permissaoSensorNegada: args.informacoesDispositivo?.permissaoNegada,
			criadoEm: Date.now()
		});

		// Atualizar banco de horas após registrar
		await atualizarBancoHoras(ctx, usuario.funcionarioId, data, config);

		return { registroId, tipo, dentroDoPrazo };
	}
});

/**
 * Lista registros do dia atual do funcionário
 */
export const listarRegistrosDia = query({
	args: {
		data: v.optional(v.string()), // YYYY-MM-DD, se não fornecido usa hoje
		_refresh: v.optional(v.number()) // Parâmetro usado pelo frontend para forçar refresh
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario || !usuario.funcionarioId) {
			return [];
		}

		const funcionarioId = usuario.funcionarioId; // Garantir que não é undefined
		const data = args.data || new Date().toISOString().split('T')[0]!;

		console.log('[listarRegistrosDia] Buscando registros:', { funcionarioId, data });

		const registros = await ctx.db
			.query('registrosPonto')
			.withIndex('by_funcionario_data', (q) =>
				q.eq('funcionarioId', funcionarioId).eq('data', data)
			)
			.order('asc')
			.collect();

		console.log(
			'[listarRegistrosDia] Registros encontrados:',
			registros.length,
			registros.map((r) => ({
				_id: r._id,
				tipo: r.tipo,
				data: r.data,
				hora: r.hora,
				minuto: r.minuto
			}))
		);

		return registros;
	}
});

/**
 * Obtém saldo diário de um funcionário para uma data específica
 */
export const obterSaldoDiario = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		data: v.string() // YYYY-MM-DD
	},
	handler: async (ctx, args) => {
		// Buscar banco de horas do dia
		const bancoHoras = await ctx.db
			.query('bancoHoras')
			.withIndex('by_funcionario_data', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('data', args.data)
			)
			.first();

		if (!bancoHoras) {
			return {
				saldoMinutos: 0,
				horas: 0,
				minutos: 0,
				positivo: true
			};
		}

		const horas = Math.floor(Math.abs(bancoHoras.saldoMinutos) / 60);
		const minutos = Math.abs(bancoHoras.saldoMinutos) % 60;
		const positivo = bancoHoras.saldoMinutos >= 0;

		return {
			saldoMinutos: bancoHoras.saldoMinutos,
			horas,
			minutos,
			positivo
		};
	}
});

/**
 * Lista registros por período (para RH)
 */
export const listarRegistrosPeriodo = query({
	args: {
		funcionarioId: v.optional(v.id('funcionarios')),
		dataInicio: v.string(), // YYYY-MM-DD
		dataFim: v.string() // YYYY-MM-DD
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			console.warn('[listarRegistrosPeriodo] Usuário não autenticado');
			return [];
		}

		// Verificar permissão (RH ou TI)
		// Por enquanto, permitir se tiver funcionarioId ou for admin
		// TODO: Implementar verificação de permissão adequada

		// Validar formato das datas
		if (!args.dataInicio || !args.dataFim) {
			console.warn('[listarRegistrosPeriodo] Datas não fornecidas');
			return [];
		}

		// Validar formato YYYY-MM-DD
		const dataInicioRegex = /^\d{4}-\d{2}-\d{2}$/;
		if (!dataInicioRegex.test(args.dataInicio) || !dataInicioRegex.test(args.dataFim)) {
			console.warn('[listarRegistrosPeriodo] Formato de data inválido', {
				dataInicio: args.dataInicio,
				dataFim: args.dataFim
			});
			return [];
		}

		console.log('[listarRegistrosPeriodo] Buscando registros', {
			dataInicio: args.dataInicio,
			dataFim: args.dataFim,
			funcionarioId: args.funcionarioId,
			usuarioId: usuario._id
		});

		let registrosFiltrados;

		// Se funcionário foi especificado, usar índice por funcionário e data (mais eficiente)
		if (args.funcionarioId) {
			// Garantir que funcionarioId não é undefined para TypeScript
			const funcionarioId = args.funcionarioId;

			// Buscar todos os registros do funcionário
			const todosRegistrosFuncionario = await ctx.db
				.query('registrosPonto')
				.withIndex('by_funcionario_data', (q) => q.eq('funcionarioId', funcionarioId))
				.collect();

			// Filtrar por período de data usando comparação de strings (formato YYYY-MM-DD)
			registrosFiltrados = todosRegistrosFuncionario.filter((r) => {
				// Comparação de strings funciona para formato YYYY-MM-DD
				return r.data >= args.dataInicio && r.data <= args.dataFim;
			});
		} else {
			// Se não há funcionário especificado, buscar todos e filtrar (menos eficiente, mas necessário)
			try {
				// Tentar usar índice por data primeiro
				const registros = await ctx.db
					.query('registrosPonto')
					.withIndex('by_data', (q) => q.gte('data', args.dataInicio).lte('data', args.dataFim))
					.collect();

				console.log('[listarRegistrosPeriodo] Registros do índice by_data:', registros.length);

				// Garantir que as datas estão no formato correto e filtrar novamente para garantir
				registrosFiltrados = registros.filter((r) => {
					// Comparação de strings funciona para formato YYYY-MM-DD
					return r.data >= args.dataInicio && r.data <= args.dataFim;
				});

				console.log('[listarRegistrosPeriodo] Registros após filtro:', registrosFiltrados.length);
			} catch (error) {
				console.error('[listarRegistrosPeriodo] Erro ao buscar registros:', error);
				// Fallback: buscar todos e filtrar manualmente
				const todosRegistros = await ctx.db.query('registrosPonto').collect();

				registrosFiltrados = todosRegistros.filter((r) => {
					return r.data >= args.dataInicio && r.data <= args.dataFim;
				});

				console.log(
					'[listarRegistrosPeriodo] Fallback - registros encontrados:',
					registrosFiltrados.length
				);
			}
		}

		console.log(
			'[listarRegistrosPeriodo] Registros encontrados antes de buscar funcionários:',
			registrosFiltrados.length
		);

		// Buscar informações dos funcionários
		const funcionariosIds = new Set(registrosFiltrados.map((r) => r.funcionarioId));
		const funcionarios = await Promise.all(Array.from(funcionariosIds).map((id) => ctx.db.get(id)));

		// Buscar saldos diários para cada data/funcionário
		const saldosPorDataFuncionario: Record<string, number> = {};
		const datasUnicas = new Set(registrosFiltrados.map((r) => `${r.funcionarioId}-${r.data}`));

		for (const chave of datasUnicas) {
			const [funcId, data] = chave.split('-');
			const bancoHoras = await ctx.db
				.query('bancoHoras')
				.withIndex('by_funcionario_data', (q) =>
					q.eq('funcionarioId', funcId as Id<'funcionarios'>).eq('data', data)
				)
				.first();

			if (bancoHoras) {
				saldosPorDataFuncionario[chave] = bancoHoras.saldoMinutos;
			}
		}

		console.log(
			'[listarRegistrosPeriodo] Total de registros a retornar:',
			registrosFiltrados.length
		);

		// Buscar fotos de perfil dos funcionários
		const funcionariosComFoto = await Promise.all(
			funcionarios.map(async (funcionario) => {
				if (!funcionario) return { funcionario: null, fotoPerfilUrl: null };

				let fotoPerfilUrl: string | null = null;
				const usuario = await ctx.db
					.query('usuarios')
					.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', funcionario._id))
					.first();
				if (usuario?.fotoPerfil) {
					fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
				}

				return { funcionario, fotoPerfilUrl };
			})
		);

		return registrosFiltrados.map((registro) => {
			const funcionarioComFoto = funcionariosComFoto.find(
				(f) => f.funcionario?._id === registro.funcionarioId
			);
			const funcionario = funcionarioComFoto?.funcionario;
			const fotoPerfilUrl = funcionarioComFoto?.fotoPerfilUrl || null;
			const chave = `${registro.funcionarioId}-${registro.data}`;
			const saldoMinutos = saldosPorDataFuncionario[chave] || 0;
			const horas = Math.floor(Math.abs(saldoMinutos) / 60);
			const minutos = Math.abs(saldoMinutos) % 60;
			const positivo = saldoMinutos >= 0;

			return {
				...registro,
				funcionario: funcionario
					? {
							nome: funcionario.nome,
							matricula: funcionario.matricula,
							descricaoCargo: funcionario.descricaoCargo
						}
					: null,
				fotoPerfilUrl,
				saldoDiario: {
					saldoMinutos,
					horas,
					minutos,
					positivo
				}
			};
		});
	}
});

/**
 * Obtém estatísticas de pontos (para gráficos)
 */
export const obterEstatisticas = query({
	args: {
		dataInicio: v.string(), // YYYY-MM-DD
		dataFim: v.string(), // YYYY-MM-DD
		funcionarioId: v.optional(v.id('funcionarios'))
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			// Retornar estatísticas zeradas quando não autenticado
			return {
				totalRegistros: 0,
				dentroDoPrazo: 0,
				foraDoPrazo: 0,
				totalFuncionarios: 0,
				funcionariosDentroPrazo: 0,
				funcionariosForaPrazo: 0
			};
		}

		// TODO: Verificar permissão (RH ou TI)

		let registros = await ctx.db
			.query('registrosPonto')
			.withIndex('by_data', (q) => q.gte('data', args.dataInicio).lte('data', args.dataFim))
			.collect();

		// Filtrar por funcionário se fornecido
		if (args.funcionarioId) {
			registros = registros.filter((r) => r.funcionarioId === args.funcionarioId);
		}

		const totalRegistros = registros.length;
		const dentroDoPrazo = registros.filter((r) => r.dentroDoPrazo).length;
		const foraDoPrazo = totalRegistros - dentroDoPrazo;

		// Agrupar por funcionário
		const funcionariosUnicos = new Set(registros.map((r) => r.funcionarioId));
		const totalFuncionarios = funcionariosUnicos.size;

		// Funcionários com registros dentro do prazo
		const funcionariosDentroPrazo = new Set(
			registros.filter((r) => r.dentroDoPrazo).map((r) => r.funcionarioId)
		).size;

		// Funcionários com registros fora do prazo
		const funcionariosForaPrazo = totalFuncionarios - funcionariosDentroPrazo;

		return {
			totalRegistros,
			dentroDoPrazo,
			foraDoPrazo,
			totalFuncionarios,
			funcionariosDentroPrazo,
			funcionariosForaPrazo
		};
	}
});

/**
 * Obtém um registro específico (para comprovante)
 */
export const obterRegistro = query({
	args: {
		registroId: v.id('registrosPonto')
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		const registro = await ctx.db.get(args.registroId);
		if (!registro) {
			throw new Error('Registro não encontrado');
		}

		// Verificar se o usuário tem permissão (próprio registro ou RH/TI)
		if (registro.funcionarioId !== usuario.funcionarioId) {
			// TODO: Verificar se é RH ou TI
			// Por enquanto, permitir
		}

		const funcionario = await ctx.db.get(registro.funcionarioId);
		let simbolo = null;
		if (funcionario) {
			simbolo = await ctx.db.get(funcionario.simboloId);
		}

		// Obter URL da imagem se existir
		let imagemUrl = null;
		if (registro.imagemId) {
			imagemUrl = await ctx.storage.getUrl(registro.imagemId);
		}

		return {
			...registro,
			imagemUrl,
			funcionario: funcionario
				? {
						nome: funcionario.nome,
						matricula: funcionario.matricula,
						descricaoCargo: funcionario.descricaoCargo,
						simbolo: simbolo
							? {
									nome: simbolo.nome,
									tipo: simbolo.tipo
								}
							: null
					}
				: null
		};
	}
});

/**
 * Calcula carga horária diária esperada em minutos
 */
function calcularCargaHorariaDiaria(config: {
	horarioEntrada: string;
	horarioSaidaAlmoco: string;
	horarioRetornoAlmoco: string;
	horarioSaida: string;
}): number {
	const [horaEntrada, minutoEntrada] = config.horarioEntrada.split(':').map(Number);
	const [horaSaidaAlmoco, minutoSaidaAlmoco] = config.horarioSaidaAlmoco.split(':').map(Number);
	const [horaRetornoAlmoco, minutoRetornoAlmoco] = config.horarioRetornoAlmoco
		.split(':')
		.map(Number);
	const [horaSaida, minutoSaida] = config.horarioSaida.split(':').map(Number);

	const minutosEntrada = horaEntrada * 60 + minutoEntrada;
	const minutosSaidaAlmoco = horaSaidaAlmoco * 60 + minutoSaidaAlmoco;
	const minutosRetornoAlmoco = horaRetornoAlmoco * 60 + minutoRetornoAlmoco;
	const minutosSaida = horaSaida * 60 + minutoSaida;

	// Calcular horas trabalhadas: (saída almoço - entrada) + (saída - retorno almoço)
	const horasManha = minutosSaidaAlmoco - minutosEntrada;
	const horasTarde = minutosSaida - minutosRetornoAlmoco;

	return horasManha + horasTarde;
}

/**
 * Calcula horas trabalhadas do dia baseado nos registros
 * Trata casos incompletos de forma mais robusta
 */
function calcularHorasTrabalhadas(
	registros: Array<{
		tipo: string;
		hora: number;
		minuto: number;
	}>
): number {
	if (registros.length === 0) {
		return 0;
	}

	// Ordenar registros por timestamp
	const registrosOrdenados = [...registros].sort((a, b) => {
		const minutosA = a.hora * 60 + a.minuto;
		const minutosB = b.hora * 60 + b.minuto;
		return minutosA - minutosB;
	});

	// Procurar registros principais
	const entrada = registrosOrdenados.find((r) => r.tipo === 'entrada');
	const saidaAlmoco = registrosOrdenados.find((r) => r.tipo === 'saida_almoco');
	const retornoAlmoco = registrosOrdenados.find((r) => r.tipo === 'retorno_almoco');
	const saida = registrosOrdenados.find((r) => r.tipo === 'saida');

	// Caso 1: Tem entrada e saída completas
	if (entrada && saida) {
		const minutosEntrada = entrada.hora * 60 + entrada.minuto;
		const minutosSaida = saida.hora * 60 + saida.minuto;

		// Caso 1.1: Tem intervalo de almoço completo (saída almoço + retorno almoço)
		if (saidaAlmoco && retornoAlmoco) {
			const minutosSaidaAlmoco = saidaAlmoco.hora * 60 + saidaAlmoco.minuto;
			const minutosRetornoAlmoco = retornoAlmoco.hora * 60 + retornoAlmoco.minuto;

			// Validar ordem lógica
			if (
				minutosSaidaAlmoco > minutosEntrada &&
				minutosRetornoAlmoco > minutosSaidaAlmoco &&
				minutosSaida > minutosRetornoAlmoco
			) {
				const horasManha = minutosSaidaAlmoco - minutosEntrada;
				const horasTarde = minutosSaida - minutosRetornoAlmoco;
				return horasManha + horasTarde;
			}
		}

		// Caso 1.2: Tem apenas saída almoço (sem retorno) - considerar apenas manhã
		if (saidaAlmoco && !retornoAlmoco) {
			const minutosSaidaAlmoco = saidaAlmoco.hora * 60 + saidaAlmoco.minuto;
			if (minutosSaidaAlmoco > minutosEntrada) {
				return minutosSaidaAlmoco - minutosEntrada;
			}
		}

		// Caso 1.3: Tem apenas retorno almoço (sem saída) - considerar apenas tarde
		if (!saidaAlmoco && retornoAlmoco) {
			const minutosRetornoAlmoco = retornoAlmoco.hora * 60 + retornoAlmoco.minuto;
			if (minutosSaida > minutosRetornoAlmoco) {
				return minutosSaida - minutosRetornoAlmoco;
			}
		}

		// Caso 1.4: Sem intervalo de almoço registrado - calcular direto
		if (!saidaAlmoco && !retornoAlmoco) {
			if (minutosSaida > minutosEntrada) {
				return minutosSaida - minutosEntrada;
			}
		}
	}

	// Caso 2: Tem apenas entrada (sem saída) - retornar 0 (dia incompleto)
	if (entrada && !saida) {
		// Se tiver saída almoço, considerar até a saída almoço
		if (saidaAlmoco) {
			const minutosEntrada = entrada.hora * 60 + entrada.minuto;
			const minutosSaidaAlmoco = saidaAlmoco.hora * 60 + saidaAlmoco.minuto;
			if (minutosSaidaAlmoco > minutosEntrada) {
				return minutosSaidaAlmoco - minutosEntrada;
			}
		}
		return 0;
	}

	// Caso 3: Tem apenas saída (sem entrada) - inconsistência, retornar 0
	if (saida && !entrada) {
		return 0;
	}

	// Caso 4: Não tem entrada nem saída - retornar 0
	return 0;
}

/**
 * Verifica se há atestado médico ativo para o funcionário na data
 */
async function verificarAtestadoAtivo(
	ctx: QueryCtx | MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string
): Promise<{ temAtestado: boolean; atestadoId?: string; motivo?: string }> {
	const dataObj = new Date(data);
	const atestados = await ctx.db
		.query('atestados')
		.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
		.collect();

	for (const atestado of atestados) {
		const dataInicio = new Date(atestado.dataInicio);
		const dataFim = new Date(atestado.dataFim);
		if (dataObj >= dataInicio && dataObj <= dataFim) {
			return {
				temAtestado: true,
				atestadoId: atestado._id,
				motivo: `Atestado Médico - CID: ${atestado.cid || 'N/A'}`
			};
		}
	}

	return { temAtestado: false };
}

/**
 * Verifica se há licença ativa para o funcionário na data
 */
async function verificarLicencaAtiva(
	ctx: QueryCtx | MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string
): Promise<{ temLicenca: boolean; licencaId?: string; motivo?: string }> {
	const dataObj = new Date(data);
	const licencas = await ctx.db
		.query('licencas')
		.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
		.collect();

	for (const licenca of licencas) {
		const dataInicio = new Date(licenca.dataInicio);
		const dataFim = new Date(licenca.dataFim);
		if (dataObj >= dataInicio && dataObj <= dataFim) {
			const tipoLicenca =
				licenca.tipo === 'maternidade'
					? 'Licença Maternidade'
					: licenca.tipo === 'paternidade'
						? 'Licença Paternidade'
						: 'Licença';
			return {
				temLicenca: true,
				licencaId: licenca._id,
				motivo: tipoLicenca
			};
		}
	}

	return { temLicenca: false };
}

/**
 * Verifica se há ausência aprovada para o funcionário na data
 */
async function verificarAusenciaAprovada(
	ctx: QueryCtx | MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string
): Promise<{ temAusencia: boolean; ausenciaId?: string; motivo?: string }> {
	const dataObj = new Date(data);
	const ausencias = await ctx.db
		.query('solicitacoesAusencias')
		.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
		.filter((q) => q.eq(q.field('status'), 'aprovado'))
		.collect();

	for (const ausencia of ausencias) {
		const dataInicio = new Date(ausencia.dataInicio);
		const dataFim = new Date(ausencia.dataFim);
		if (dataObj >= dataInicio && dataObj <= dataFim) {
			return {
				temAusencia: true,
				ausenciaId: ausencia._id,
				motivo: `Ausência Aprovada - ${ausencia.motivo}`
			};
		}
	}

	return { temAusencia: false };
}

/**
 * Verifica ajustes manuais aplicados no dia
 */
async function obterAjustesManuais(
	ctx: QueryCtx | MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string
): Promise<Array<{ tipo: 'abonar' | 'descontar' | 'compensar'; valorMinutos: number; motivo?: string; ajusteId?: Id<'ajustesBancoHoras'> }>> {
	const ajustes = await ctx.db
		.query('ajustesBancoHoras')
		.withIndex('by_funcionario_data', (q) => q.eq('funcionarioId', funcionarioId).eq('dataAplicacao', data))
		.filter((q) => q.and(q.eq(q.field('aplicado'), true), q.eq(q.field('motivoTipo'), 'manual')))
		.collect();

	return ajustes.map((a) => ({
		tipo: a.tipo,
		valorMinutos: a.valorMinutos,
		motivo: a.motivoDescricao,
		ajusteId: a._id
	}));
}

/**
 * Detecta inconsistências no banco de horas do dia
 */
async function detectarInconsistencias(
	ctx: MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string,
	registrosPontoIds: Array<Id<'registrosPonto'>>,
	atestadoInfo: { temAtestado: boolean; atestadoId?: string },
	licencaInfo: { temLicenca: boolean; licencaId?: string },
	ausenciaInfo: { temAusencia: boolean; ausenciaId?: string }
): Promise<Array<Id<'inconsistenciasBancoHoras'>>> {
	const inconsistenciaIds: Array<Id<'inconsistenciasBancoHoras'>> = [];

	// Verificar se há registro de ponto quando há atestado
	if (atestadoInfo.temAtestado && registrosPontoIds.length > 0) {
		const inconsistenciaId = await ctx.db.insert('inconsistenciasBancoHoras', {
			funcionarioId,
			tipo: 'ponto_com_atestado',
			descricao: `Registro de ponto detectado em dia com atestado médico ativo`,
			dataDetectada: data,
			dataInconsistencia: data,
			status: 'pendente',
			criadoEm: Date.now()
		});
		inconsistenciaIds.push(inconsistenciaId);
	}

	// Verificar se há registro de ponto quando há licença
	if (licencaInfo.temLicenca && registrosPontoIds.length > 0) {
		const inconsistenciaId = await ctx.db.insert('inconsistenciasBancoHoras', {
			funcionarioId,
			tipo: 'ponto_com_licenca',
			descricao: `Registro de ponto detectado em dia com licença ativa`,
			dataDetectada: data,
			dataInconsistencia: data,
			status: 'pendente',
			criadoEm: Date.now()
		});
		inconsistenciaIds.push(inconsistenciaId);
	}

	// Verificar se há registro de ponto quando há ausência aprovada
	if (ausenciaInfo.temAusencia && registrosPontoIds.length > 0) {
		const inconsistenciaId = await ctx.db.insert('inconsistenciasBancoHoras', {
			funcionarioId,
			tipo: 'ponto_com_ausencia',
			descricao: `Registro de ponto detectado em dia com ausência aprovada`,
			dataDetectada: data,
			dataInconsistencia: data,
			status: 'pendente',
			criadoEm: Date.now()
		});
		inconsistenciaIds.push(inconsistenciaId);
	}

	return inconsistenciaIds;
}

/**
 * Aplica ajuste automático baseado em atestado, licença ou ausência
 */
async function aplicarAjusteAutomatico(
	ctx: MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string,
	cargaHorariaDiaria: number,
	motivoTipo: 'atestado' | 'licenca' | 'ausencia',
	motivoId: string,
	motivoDescricao: string
): Promise<Id<'ajustesBancoHoras'>> {
	const ajusteId = await ctx.db.insert('ajustesBancoHoras', {
		funcionarioId,
		tipo: 'abonar',
		motivoTipo,
		motivoId,
		motivoDescricao,
		valorMinutos: cargaHorariaDiaria, // Abonar a carga horária completa do dia
		dataAplicacao: data,
		aplicado: true,
		criadoEm: Date.now(),
		aplicadoEm: Date.now()
	});

	return ajusteId;
}

/**
 * Atualiza ou cria registro de banco de horas para o dia (versão completa)
 */
async function atualizarBancoHoras(
	ctx: MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	data: string,
	config: {
		horarioEntrada: string;
		horarioSaidaAlmoco: string;
		horarioRetornoAlmoco: string;
		horarioSaida: string;
	}
): Promise<void> {
	// Buscar todos os registros do dia
	const registrosDoDia = await ctx.db
		.query('registrosPonto')
		.withIndex('by_funcionario_data', (q) => q.eq('funcionarioId', funcionarioId).eq('data', data))
		.collect();

	// Calcular carga horária esperada
	const cargaHorariaDiaria = calcularCargaHorariaDiaria(config);

	// Verificar atestados, licenças e ausências ativos
	const [atestadoInfo, licencaInfo, ausenciaInfo, ajustesManuais] = await Promise.all([
		verificarAtestadoAtivo(ctx, funcionarioId, data),
		verificarLicencaAtiva(ctx, funcionarioId, data),
		verificarAusenciaAprovada(ctx, funcionarioId, data),
		obterAjustesManuais(ctx, funcionarioId, data)
	]);

	// Calcular horas trabalhadas
	const horasTrabalhadas = calcularHorasTrabalhadas(registrosDoDia);

	// Determinar tipo do dia e motivo
	let tipoDia: 'normal' | 'atestado' | 'licenca' | 'ausencia' | 'abonado' | 'descontado' = 'normal';
	let motivoAbono: string | undefined = undefined;
	const ajustesIds: Array<Id<'ajustesBancoHoras'>> = [];

	// Aplicar ajustes automáticos se houver atestado, licença ou ausência
	if (atestadoInfo.temAtestado) {
		tipoDia = 'atestado';
		motivoAbono = atestadoInfo.motivo;
		if (atestadoInfo.atestadoId) {
			const ajusteId = await aplicarAjusteAutomatico(
				ctx,
				funcionarioId,
				data,
				cargaHorariaDiaria,
				'atestado',
				atestadoInfo.atestadoId,
				atestadoInfo.motivo || 'Atestado Médico'
			);
			ajustesIds.push(ajusteId);
		}
	} else if (licencaInfo.temLicenca) {
		tipoDia = 'licenca';
		motivoAbono = licencaInfo.motivo;
		if (licencaInfo.licencaId) {
			const ajusteId = await aplicarAjusteAutomatico(
				ctx,
				funcionarioId,
				data,
				cargaHorariaDiaria,
				'licenca',
				licencaInfo.licencaId,
				licencaInfo.motivo || 'Licença'
			);
			ajustesIds.push(ajusteId);
		}
	} else if (ausenciaInfo.temAusencia) {
		tipoDia = 'ausencia';
		motivoAbono = ausenciaInfo.motivo;
		if (ausenciaInfo.ausenciaId) {
			const ajusteId = await aplicarAjusteAutomatico(
				ctx,
				funcionarioId,
				data,
				cargaHorariaDiaria,
				'ausencia',
				ausenciaInfo.ausenciaId,
				ausenciaInfo.motivo || 'Ausência Aprovada'
			);
			ajustesIds.push(ajusteId);
		}
	}

	// Aplicar ajustes manuais
	let ajusteManualTotal = 0;
	for (const ajuste of ajustesManuais) {
		ajusteManualTotal += ajuste.valorMinutos;
		if (ajuste.tipo === 'abonar') {
			if (tipoDia === 'normal') {
				tipoDia = 'abonado';
			}
			if (!motivoAbono) {
				motivoAbono = ajuste.motivo || 'Abono Manual';
			}
		} else if (ajuste.tipo === 'descontar') {
			if (tipoDia === 'normal') {
				tipoDia = 'descontado';
			}
		}
		// Adicionar ID do ajuste manual
		if (ajuste.ajusteId) {
			ajustesIds.push(ajuste.ajusteId);
		}
	}

	// Calcular saldo considerando ajustes
	// Saldo base = horas trabalhadas - carga horária
	let saldoMinutos = horasTrabalhadas - cargaHorariaDiaria;

	// Adicionar ajustes automáticos (abonos por atestado/licença/ausência)
	if (atestadoInfo.temAtestado || licencaInfo.temLicenca || ausenciaInfo.temAusencia) {
		saldoMinutos += cargaHorariaDiaria; // Abonar carga horária completa
	}

	// Adicionar/subtrair ajustes manuais
	saldoMinutos += ajusteManualTotal;

	// Detectar inconsistências
	const registrosPontoIds = registrosDoDia.map((r) => r._id);
	const inconsistenciaIds = await detectarInconsistencias(
		ctx,
		funcionarioId,
		data,
		registrosPontoIds,
		atestadoInfo,
		licencaInfo,
		ausenciaInfo
	);

	// Buscar banco de horas existente
	const bancoHorasExistente = await ctx.db
		.query('bancoHoras')
		.withIndex('by_funcionario_data', (q) => q.eq('funcionarioId', funcionarioId).eq('data', data))
		.first();

	if (bancoHorasExistente) {
		// Atualizar existente
		await ctx.db.patch(bancoHorasExistente._id, {
			cargaHorariaDiaria,
			horasTrabalhadas,
			saldoMinutos,
			registrosPontoIds,
			ajustesIds: ajustesIds.length > 0 ? ajustesIds : undefined,
			motivoAbono,
			tipoDia,
			inconsistenciasIds: inconsistenciaIds.length > 0 ? inconsistenciaIds : undefined,
			calculadoEm: Date.now()
		});
	} else {
		// Criar novo
		await ctx.db.insert('bancoHoras', {
			funcionarioId,
			data,
			cargaHorariaDiaria,
			horasTrabalhadas,
			saldoMinutos,
			registrosPontoIds,
			ajustesIds: ajustesIds.length > 0 ? ajustesIds : undefined,
			motivoAbono,
			tipoDia,
			inconsistenciasIds: inconsistenciaIds.length > 0 ? inconsistenciaIds : undefined,
			calculadoEm: Date.now()
		});
	}

	// Atualizar banco de horas mensal
	const mes = data.substring(0, 7); // YYYY-MM
	await calcularBancoHorasMensal(ctx, funcionarioId, mes);
}

/**
 * Obtém histórico e saldo do dia
 */
export const obterHistoricoESaldoDia = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		data: v.string(), // YYYY-MM-DD
		_refresh: v.optional(v.number()) // Parâmetro usado pelo frontend para forçar refresh
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario || !usuario.funcionarioId) {
			console.warn('[obterHistoricoESaldoDia] Usuário não autenticado ou sem funcionarioId');
			// Retornar dados vazios em vez de lançar erro
			return {
				registros: [],
				cargaHorariaDiaria: 0,
				horasTrabalhadas: 0,
				saldoMinutos: 0
			};
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		// Buscar registros do dia
		const registros = await ctx.db
			.query('registrosPonto')
			.withIndex('by_funcionario_data', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('data', args.data)
			)
			.order('asc')
			.collect();

		console.log('[obterHistoricoESaldoDia] Registros encontrados:', registros.length, {
			funcionarioId: args.funcionarioId,
			data: args.data
		});

		// Buscar configuração de ponto
		const config = await ctx.db
			.query('configuracaoPonto')
			.withIndex('by_ativo', (q) => q.eq('ativo', true))
			.first();

		if (!config) {
			return {
				registros: [],
				cargaHorariaDiaria: 0,
				horasTrabalhadas: 0,
				saldoMinutos: 0
			};
		}

		// Calcular valores
		const cargaHorariaDiaria = calcularCargaHorariaDiaria(config);
		const horasTrabalhadas = calcularHorasTrabalhadas(registros);
		const saldoMinutos = horasTrabalhadas - cargaHorariaDiaria;

		const horas = Math.floor(Math.abs(saldoMinutos) / 60);
		const minutos = Math.abs(saldoMinutos) % 60;
		const positivo = saldoMinutos >= 0;

		return {
			registros,
			cargaHorariaDiaria,
			horasTrabalhadas,
			saldoMinutos,
			saldoFormatado: {
				horas,
				minutos,
				positivo
			}
		};
	}
});

/**
 * Obtém banco de horas acumulado do funcionário
 */
export const obterBancoHorasFuncionario = query({
	args: {
		funcionarioId: v.id('funcionarios')
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		// Buscar todos os registros de banco de horas do funcionário
		const bancosHoras = await ctx.db
			.query('bancoHoras')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId))
			.order('desc')
			.collect();

		// Calcular saldo acumulado
		const saldoAcumuladoMinutos = bancosHoras.reduce((acc, bh) => acc + bh.saldoMinutos, 0);

		return {
			bancosHoras,
			saldoAcumuladoMinutos,
			totalDias: bancosHoras.length
		};
	}
});

/**
 * Calcula e atualiza banco de horas mensal para um funcionário
 * Esta função deve ser chamada após atualizações no banco de horas diário
 */
async function calcularBancoHorasMensal(
	ctx: MutationCtx,
	funcionarioId: Id<'funcionarios'>,
	mes: string // YYYY-MM
): Promise<void> {
	// Buscar todos os bancoHoras do mês
	const dataInicio = `${mes}-01`;
	// Calcular último dia do mês: criar data do primeiro dia do mês seguinte e subtrair 1 dia
	const [ano, mesNum] = mes.split('-').map(Number);
	const ultimoDia = new Date(ano, mesNum, 0).getDate(); // Dia 0 do mês seguinte = último dia do mês atual
	const dataFim = `${mes}-${String(ultimoDia).padStart(2, '0')}`;

	const bancosHorasDoMes = await ctx.db
		.query('bancoHoras')
		.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
		.filter((q) => {
			const data = q.field('data');
			return q.and(q.gte(data, dataInicio), q.lte(data, dataFim));
		})
		.collect();

	// Calcular saldo do mês anterior para obter saldo inicial
	const mesAnterior = new Date(`${mes}-01`);
	mesAnterior.setMonth(mesAnterior.getMonth() - 1);
	const mesAnteriorStr = `${mesAnterior.getFullYear()}-${String(mesAnterior.getMonth() + 1).padStart(2, '0')}`;

	const bancoMensalAnterior = await ctx.db
		.query('bancoHorasMensal')
		.withIndex('by_funcionario_mes', (q) =>
			q.eq('funcionarioId', funcionarioId).eq('mes', mesAnteriorStr)
		)
		.first();

	const saldoInicialMinutos = bancoMensalAnterior?.saldoFinalMinutos || 0;

	// Calcular estatísticas do mês
	const diasTrabalhados = bancosHorasDoMes.length;
	const saldoMesMinutos = bancosHorasDoMes.reduce((acc, bh) => acc + bh.saldoMinutos, 0);
	const saldoFinalMinutos = saldoInicialMinutos + saldoMesMinutos;

	// Separar horas extras e déficit
	const horasExtras = bancosHorasDoMes
		.filter((bh) => bh.saldoMinutos > 0)
		.reduce((acc, bh) => acc + bh.saldoMinutos, 0);
	const horasDeficit = Math.abs(
		bancosHorasDoMes
			.filter((bh) => bh.saldoMinutos < 0)
			.reduce((acc, bh) => acc + bh.saldoMinutos, 0)
	);

	// Calcular totais de ajustes, abonos e descontos
	let totalAjustes = 0;
	let totalAbonos = 0;
	let totalDescontos = 0;
	let inconsistenciasResolvidas = 0;

	for (const bh of bancosHorasDoMes) {
		// Contar ajustes
		if (bh.ajustesIds && bh.ajustesIds.length > 0) {
			const ajustes = await Promise.all(
				bh.ajustesIds.map((id) => ctx.db.get(id))
			);
			for (const ajuste of ajustes) {
				if (ajuste) {
					totalAjustes += Math.abs(ajuste.valorMinutos);
					if (ajuste.tipo === 'abonar') {
						totalAbonos += ajuste.valorMinutos;
					} else if (ajuste.tipo === 'descontar') {
						totalDescontos += Math.abs(ajuste.valorMinutos);
					}
				}
			}
		}

		// Contar inconsistências resolvidas
		if (bh.inconsistenciasIds && bh.inconsistenciasIds.length > 0) {
			const inconsistencias = await Promise.all(
				bh.inconsistenciasIds.map((id) => ctx.db.get(id))
			);
			inconsistenciasResolvidas += inconsistencias.filter(
				(i) => i && i.status === 'resolvida'
			).length;
		}
	}

	const agora = Date.now();

	// Buscar ou criar registro mensal
	const bancoMensalExistente = await ctx.db
		.query('bancoHorasMensal')
		.withIndex('by_funcionario_mes', (q) =>
			q.eq('funcionarioId', funcionarioId).eq('mes', mes)
		)
		.first();

	if (bancoMensalExistente) {
		// Atualizar existente
		await ctx.db.patch(bancoMensalExistente._id, {
			saldoInicialMinutos,
			saldoFinalMinutos,
			saldoMesMinutos,
			diasTrabalhados,
			horasExtras,
			horasDeficit,
			totalAjustes,
			totalAbonos,
			totalDescontos,
			inconsistenciasResolvidas,
			atualizadoEm: agora
		});
	} else {
		// Criar novo
		await ctx.db.insert('bancoHorasMensal', {
			funcionarioId,
			mes,
			saldoInicialMinutos,
			saldoFinalMinutos,
			saldoMesMinutos,
			diasTrabalhados,
			horasExtras,
			horasDeficit,
			totalAjustes,
			totalAbonos,
			totalDescontos,
			inconsistenciasResolvidas,
			calculadoEm: agora,
			atualizadoEm: agora
		});
	}
}

/**
 * Obtém banco de horas mensal de um funcionário
 */
export const obterBancoHorasMensal = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		mes: v.string() // YYYY-MM
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		const bancoMensal = await ctx.db
			.query('bancoHorasMensal')
			.withIndex('by_funcionario_mes', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('mes', args.mes)
			)
			.first();

		if (!bancoMensal) {
			// Retornar valores zerados se não existe
			return {
				mes: args.mes,
				saldoInicialMinutos: 0,
				saldoFinalMinutos: 0,
				saldoMesMinutos: 0,
				diasTrabalhados: 0,
				horasExtras: 0,
				horasDeficit: 0,
				saldoFormatado: {
					inicial: { horas: 0, minutos: 0, positivo: true },
					final: { horas: 0, minutos: 0, positivo: true },
					mes: { horas: 0, minutos: 0, positivo: true }
				}
			};
		}

		// Formatar valores
		const formatarSaldo = (minutos: number) => {
			const horas = Math.floor(Math.abs(minutos) / 60);
			const mins = Math.abs(minutos) % 60;
			return { horas, minutos: mins, positivo: minutos >= 0 };
		};

		return {
			...bancoMensal,
			saldoFormatado: {
				inicial: formatarSaldo(bancoMensal.saldoInicialMinutos),
				final: formatarSaldo(bancoMensal.saldoFinalMinutos),
				mes: formatarSaldo(bancoMensal.saldoMesMinutos)
			}
		};
	}
});

/**
 * Lista histórico mensal de banco de horas de um funcionário
 */
export const listarHistoricoMensal = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		mesInicio: v.optional(v.string()), // YYYY-MM
		mesFim: v.optional(v.string()) // YYYY-MM
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		let query = ctx.db
			.query('bancoHorasMensal')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId));

		// Filtrar por período se fornecido
		if (args.mesInicio || args.mesFim) {
			query = query.filter((q) => {
				const mes = q.field('mes');
				if (args.mesInicio && args.mesFim) {
					return q.and(q.gte(mes, args.mesInicio), q.lte(mes, args.mesFim));
				} else if (args.mesInicio) {
					return q.gte(mes, args.mesInicio);
				} else if (args.mesFim) {
					return q.lte(mes, args.mesFim);
				}
				return true;
			});
		}

		const bancosMensais = await query.order('desc').collect();

		// Formatar valores
		const formatarSaldo = (minutos: number) => {
			const horas = Math.floor(Math.abs(minutos) / 60);
			const mins = Math.abs(minutos) % 60;
			return { horas, minutos: mins, positivo: minutos >= 0 };
		};

		return bancosMensais.map((bm) => ({
			...bm,
			saldoFormatado: {
				inicial: formatarSaldo(bm.saldoInicialMinutos),
				final: formatarSaldo(bm.saldoFinalMinutos),
				mes: formatarSaldo(bm.saldoMesMinutos),
				extras: formatarSaldo(bm.horasExtras),
				deficit: formatarSaldo(-bm.horasDeficit)
			}
		}));
	}
});

/**
 * Envia notificações push para alertas de banco de horas
 * Esta função deve ser chamada periodicamente (via cron ou scheduler)
 */
export const enviarNotificacoesAlertasBancoHoras = internalMutation({
	args: {},
	handler: async (ctx) => {
		// Buscar todos os funcionários ativos (sem data de desligamento)
		const todosFuncionarios = await ctx.db.query('funcionarios').collect();
		const funcionarios = todosFuncionarios.filter((f) => !f.desligamentoData);

		let notificacoesEnviadas = 0;

		for (const funcionario of funcionarios) {
			// Buscar usuário associado
			const usuario = await ctx.db
				.query('usuarios')
				.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', funcionario._id))
				.first();

			if (!usuario) continue;

			// Verificar alertas
			const hoje = new Date();
			const mesAtual = `${hoje.getFullYear()}-${String(hoje.getMonth() + 1).padStart(2, '0')}`;

			const bancoMensal = await ctx.db
				.query('bancoHorasMensal')
				.withIndex('by_funcionario_mes', (q) =>
					q.eq('funcionarioId', funcionario._id).eq('mes', mesAtual)
				)
				.first();

			if (bancoMensal && bancoMensal.saldoFinalMinutos < 0) {
				const horasNegativas = Math.abs(bancoMensal.saldoFinalMinutos) / 60;
				const minutosNegativos = Math.abs(bancoMensal.saldoFinalMinutos) % 60;

				// Enviar notificação apenas se saldo negativo for significativo (> 1 hora)
				if (horasNegativas >= 1) {
					const titulo = horasNegativas > 8
						? '⚠️ Alerta Crítico: Saldo Negativo de Banco de Horas'
						: '⚠️ Atenção: Saldo Negativo de Banco de Horas';
					const corpo = `Seu saldo acumulado está negativo em ${Math.floor(horasNegativas)}h ${minutosNegativos}min. Considere compensar horas ou entrar em contato com seu gestor.`;

					// Enviar push notification
					await ctx.scheduler.runAfter(0, internal.pushNotifications.enviarPushNotification, {
						usuarioId: usuario._id,
						titulo,
						corpo,
						data: {
							tipo: 'banco_horas_alerta'
						}
					});

					notificacoesEnviadas++;
				}
			}
		}

		return { notificacoesEnviadas };
	}
});

/**
 * Verifica alertas de banco de horas (saldo negativo, etc)
 */
export const verificarAlertasBancoHoras = query({
	args: {
		funcionarioId: v.id('funcionarios')
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		// Buscar banco de horas mensal mais recente
		const hoje = new Date();
		const mesAtual = `${hoje.getFullYear()}-${String(hoje.getMonth() + 1).padStart(2, '0')}`;

		const bancoMensal = await ctx.db
			.query('bancoHorasMensal')
			.withIndex('by_funcionario_mes', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('mes', mesAtual)
			)
			.first();

		const alertas: Array<{
			tipo: 'saldo_negativo' | 'saldo_negativo_critico' | 'dias_sem_registro';
			severidade: 'warning' | 'error';
			mensagem: string;
			valor?: number;
		}> = [];

		if (bancoMensal) {
			// Alerta 1: Saldo negativo acumulado
			if (bancoMensal.saldoFinalMinutos < 0) {
				const horasNegativas = Math.abs(bancoMensal.saldoFinalMinutos) / 60;
				alertas.push({
					tipo: horasNegativas > 8 ? 'saldo_negativo_critico' : 'saldo_negativo',
					severidade: horasNegativas > 8 ? 'error' : 'warning',
					mensagem: `Saldo negativo acumulado de ${Math.floor(Math.abs(bancoMensal.saldoFinalMinutos) / 60)}h ${Math.abs(bancoMensal.saldoFinalMinutos) % 60}min`,
					valor: bancoMensal.saldoFinalMinutos
				});
			}
		}

		// Verificar dias sem registro nos últimos 7 dias
		const ultimos7Dias: string[] = [];
		for (let i = 0; i < 7; i++) {
			const data = new Date();
			data.setDate(data.getDate() - i);
			ultimos7Dias.push(data.toISOString().split('T')[0]!);
		}

		const registrosRecentes = await ctx.db
			.query('bancoHoras')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId))
			.filter((q) => {
				const data = q.field('data');
				return q.or(
					...ultimos7Dias.map((dia) => q.eq(data, dia))
				);
			})
			.collect();

		const diasComRegistro = new Set(registrosRecentes.map((r) => r.data));
		const diasSemRegistro = ultimos7Dias.filter((dia) => !diasComRegistro.has(dia));

		if (diasSemRegistro.length >= 3) {
			alertas.push({
				tipo: 'dias_sem_registro',
				severidade: 'warning',
				mensagem: `${diasSemRegistro.length} dias sem registro de ponto nos últimos 7 dias`,
				valor: diasSemRegistro.length
			});
		}

		return {
			alertas,
			temAlertas: alertas.length > 0,
			bancoMensalAtual: bancoMensal
		};
	}
});

/**
 * Helper: Verificar se usuário é gestor do funcionário
 */
async function verificarGestorDoFuncionario(
	ctx: QueryCtx | MutationCtx,
	gestorId: Id<'usuarios'>,
	funcionarioId: Id<'funcionarios'>
): Promise<boolean> {
	const membroTime = await ctx.db
		.query('timesMembros')
		.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
		.filter((q) => q.eq(q.field('ativo'), true))
		.first();

	if (!membroTime) return false;

	const time = await ctx.db.get(membroTime.timeId);
	if (!time) return false;

	return time.gestorId === gestorId;
}

/**
 * Edita um registro de ponto (homologação pelo gestor)
 */
export const editarRegistroPonto = mutation({
	args: {
		registroId: v.id('registrosPonto'),
		horaNova: v.number(),
		minutoNova: v.number(),
		motivoId: v.optional(v.string()),
		motivoTipo: v.optional(v.string()),
		motivoDescricao: v.optional(v.string()),
		observacoes: v.optional(v.string())
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Buscar registro
		const registro = await ctx.db.get(args.registroId);
		if (!registro) {
			throw new Error('Registro não encontrado');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, registro.funcionarioId);
		if (!isGestor) {
			throw new Error('Você não tem permissão para editar este registro');
		}

		// Salvar dados anteriores
		const horaAnterior = registro.hora;
		const minutoAnterior = registro.minuto;

		// Atualizar registro
		await ctx.db.patch(args.registroId, {
			hora: args.horaNova,
			minuto: args.minutoNova,
			editadoPorGestor: true
		});

		// Criar registro de homologação
		const homologacaoId = await ctx.db.insert('homologacoesPonto', {
			registroId: args.registroId,
			funcionarioId: registro.funcionarioId,
			gestorId: usuario._id,
			horaAnterior,
			minutoAnterior,
			horaNova: args.horaNova,
			minutoNova: args.minutoNova,
			motivoId: args.motivoId,
			motivoTipo: args.motivoTipo,
			motivoDescricao: args.motivoDescricao,
			observacoes: args.observacoes,
			criadoEm: Date.now()
		});

		// Atualizar registro com ID da homologação
		await ctx.db.patch(args.registroId, {
			homologacaoId
		});

		// Recalcular banco de horas do dia (isso já atualiza o mensal automaticamente)
		const config = await ctx.db
			.query('configuracaoPonto')
			.withIndex('by_ativo', (q) => q.eq('ativo', true))
			.first();

		if (config) {
			await atualizarBancoHoras(ctx, registro.funcionarioId, registro.data, config);
		}

		return { success: true, homologacaoId };
	}
});

/**
 * Ajusta banco de horas (compensar, abonar ou descontar)
 */
export const ajustarBancoHoras = mutation({
	args: {
		funcionarioId: v.id('funcionarios'),
		tipoAjuste: v.union(v.literal('compensar'), v.literal('abonar'), v.literal('descontar')),
		periodoDias: v.number(),
		periodoHoras: v.number(),
		periodoMinutos: v.number(),
		motivoId: v.optional(v.string()),
		motivoTipo: v.optional(v.string()),
		motivoDescricao: v.optional(v.string()),
		observacoes: v.optional(v.string())
	},
	returns: v.object({
		success: v.boolean(),
		homologacaoId: v.id('homologacoesPonto'),
		ajusteId: v.id('ajustesBancoHoras'),
		ajusteMinutos: v.number()
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, args.funcionarioId);
		if (!isGestor) {
			throw new Error('Você não tem permissão para ajustar banco de horas deste funcionário');
		}

		// Calcular ajuste em minutos
		const ajusteMinutos = args.periodoDias * 24 * 60 + args.periodoHoras * 60 + args.periodoMinutos;

		// Aplicar sinal baseado no tipo de ajuste
		let ajusteFinal = ajusteMinutos;
		if (args.tipoAjuste === 'descontar') {
			ajusteFinal = -ajusteMinutos;
		}

		// Buscar banco de horas mais recente ou criar um registro de ajuste
		const hoje = new Date().toISOString().split('T')[0]!;
		
		// Criar registro de ajuste na nova tabela
		const ajusteId = await ctx.db.insert('ajustesBancoHoras', {
			funcionarioId: args.funcionarioId,
			tipo: args.tipoAjuste,
			motivoTipo: 'manual',
			motivoId: args.motivoId,
			motivoDescricao: args.motivoDescricao || `Ajuste ${args.tipoAjuste}`,
			valorMinutos: ajusteFinal,
			dataAplicacao: hoje,
			gestorId: usuario._id,
			observacoes: args.observacoes,
			aplicado: false,
			criadoEm: Date.now()
		});

		const bancoHorasAtual = await ctx.db
			.query('bancoHoras')
			.withIndex('by_funcionario_data', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('data', hoje)
			)
			.first();

		if (bancoHorasAtual) {
			// Atualizar saldo do dia atual e adicionar ajuste
			await ctx.db.patch(bancoHorasAtual._id, {
				saldoMinutos: bancoHorasAtual.saldoMinutos + ajusteFinal,
				ajustesIds: [...(bancoHorasAtual.ajustesIds || []), ajusteId],
				tipoDia: args.tipoAjuste === 'abonar' ? 'abonado' : args.tipoAjuste === 'descontar' ? 'descontado' : bancoHorasAtual.tipoDia,
				motivoAbono: args.motivoDescricao || bancoHorasAtual.motivoAbono
			});
		} else {
			// Criar novo registro de banco de horas para o ajuste
			const config = await ctx.db
				.query('configuracaoPonto')
				.withIndex('by_ativo', (q) => q.eq('ativo', true))
				.first();

			if (!config) {
				throw new Error('Configuração de ponto não encontrada');
			}

			const cargaHorariaDiaria = calcularCargaHorariaDiaria(config);

			await ctx.db.insert('bancoHoras', {
				funcionarioId: args.funcionarioId,
				data: hoje,
				cargaHorariaDiaria,
				horasTrabalhadas: 0,
				saldoMinutos: ajusteFinal,
				registrosPontoIds: [],
				ajustesIds: [ajusteId],
				tipoDia: args.tipoAjuste === 'abonar' ? 'abonado' : 'descontado',
				motivoAbono: args.motivoDescricao || `Ajuste ${args.tipoAjuste}`,
				calculadoEm: Date.now()
			});
		}

		// Marcar ajuste como aplicado
		await ctx.db.patch(ajusteId, {
			aplicado: true,
			aplicadoEm: Date.now()
		});

		// Recalcular banco de horas mensal após ajuste
		const mes = hoje.substring(0, 7); // YYYY-MM
		await calcularBancoHorasMensal(ctx, args.funcionarioId, mes);

		// Criar registro de homologação (mantido para compatibilidade)
		const homologacaoId = await ctx.db.insert('homologacoesPonto', {
			funcionarioId: args.funcionarioId,
			gestorId: usuario._id,
			motivoId: args.motivoId,
			motivoTipo: args.motivoTipo,
			motivoDescricao: args.motivoDescricao,
			observacoes: args.observacoes,
			tipoAjuste: args.tipoAjuste,
			periodoDias: args.periodoDias,
			periodoHoras: args.periodoHoras,
			periodoMinutos: args.periodoMinutos,
			ajusteMinutos: ajusteFinal,
			criadoEm: Date.now()
		});

		return { success: true, homologacaoId, ajusteId, ajusteMinutos: ajusteFinal };
	}
});

/**
 * Lista homologações de um funcionário ou time
 */
export const listarHomologacoes = query({
	args: {
		funcionarioId: v.optional(v.id('funcionarios'))
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		let homologacoes;

		if (args.funcionarioId) {
			// Verificar se é gestor do funcionário ou o próprio funcionário
			const funcionarioId = args.funcionarioId; // Garantir que não é undefined
			const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, funcionarioId);
			const isProprioFuncionario = usuario.funcionarioId === funcionarioId;

			if (!isGestor && !isProprioFuncionario) {
				throw new Error('Você não tem permissão para ver estas homologações');
			}

			homologacoes = await ctx.db
				.query('homologacoesPonto')
				.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
				.order('desc')
				.collect();
		} else {
			// Listar homologações do gestor
			homologacoes = await ctx.db
				.query('homologacoesPonto')
				.withIndex('by_gestor', (q) => q.eq('gestorId', usuario._id))
				.order('desc')
				.collect();
		}

		// Buscar informações adicionais
		const homologacoesComDetalhes = await Promise.all(
			homologacoes.map(async (h) => {
				const funcionario = await ctx.db.get(h.funcionarioId);
				const gestor = await ctx.db.get(h.gestorId);
				const registro = h.registroId ? await ctx.db.get(h.registroId) : null;

				// Buscar foto do perfil do funcionário através do usuário associado
				let fotoPerfilUrl: string | null = null;
				if (funcionario) {
					const usuario = await ctx.db
						.query('usuarios')
						.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', funcionario._id))
						.first();
					if (usuario?.fotoPerfil) {
						fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
					}
				}

				return {
					...h,
					funcionario: funcionario
						? {
								nome: funcionario.nome,
								matricula: funcionario.matricula
							}
						: null,
					fotoPerfilUrl,
					gestor: gestor
						? {
								nome: gestor.nome
							}
						: null,
					registro: registro
						? {
								data: registro.data,
								tipo: registro.tipo
							}
						: null
				};
			})
		);

		return homologacoesComDetalhes;
	}
});

/**
 * Exclui uma homologação (apenas para gestores)
 */
export const excluirHomologacao = mutation({
	args: {
		homologacaoId: v.id('homologacoesPonto')
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		const homologacao = await ctx.db.get(args.homologacaoId);
		if (!homologacao) {
			throw new Error('Homologação não encontrada');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(
			ctx,
			usuario._id,
			homologacao.funcionarioId
		);
		if (!isGestor && homologacao.gestorId !== usuario._id) {
			throw new Error('Você não tem permissão para excluir esta homologação');
		}

		// Se a homologação estiver vinculada a um registro, remover a referência
		if (homologacao.registroId) {
			const registro = await ctx.db.get(homologacao.registroId);
			if (registro && registro.homologacaoId === args.homologacaoId) {
				await ctx.db.patch(homologacao.registroId, {
					homologacaoId: undefined,
					editadoPorGestor: false
				});
			}
		}

		// Excluir homologação
		await ctx.db.delete(args.homologacaoId);

		return { success: true };
	}
});

/**
 * Obtém opções de motivos de atestados/declarações
 */
export const obterMotivosAtestados = query({
	args: {},
	handler: async (ctx) => {
		// Buscar tipos de atestados e declarações
		const atestados = await ctx.db.query('atestados').collect();
		const tiposUnicos = new Set<string>();

		atestados.forEach((a) => {
			if (a.cid) tiposUnicos.add(`CID: ${a.cid}`);
			if (a.observacoes) tiposUnicos.add(a.observacoes);
		});

		return {
			tipos: Array.from(tiposUnicos),
			opcoesPadrao: [
				'Atestado Médico',
				'Declaração',
				'Ajuste Administrativo',
				'Compensação de Horas',
				'Abono',
				'Desconto em Folha'
			]
		};
	}
});

/**
 * Cria uma dispensa de registro de ponto
 */
export const criarDispensaRegistro = mutation({
	args: {
		funcionarioId: v.id('funcionarios'),
		dataInicio: v.string(), // YYYY-MM-DD
		horaInicio: v.number(),
		minutoInicio: v.number(),
		dataFim: v.string(), // YYYY-MM-DD
		horaFim: v.number(),
		minutoFim: v.number(),
		motivo: v.string(),
		isento: v.boolean()
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, args.funcionarioId);
		if (!isGestor) {
			throw new Error('Você não tem permissão para criar dispensa para este funcionário');
		}

		// Validar datas
		const dataInicioObj = new Date(args.dataInicio);
		const dataFimObj = new Date(args.dataFim);

		if (dataFimObj < dataInicioObj) {
			throw new Error('Data fim deve ser maior ou igual à data início');
		}

		// Criar dispensa
		const dispensaId = await ctx.db.insert('dispensasRegistro', {
			funcionarioId: args.funcionarioId,
			gestorId: usuario._id,
			dataInicio: args.dataInicio,
			horaInicio: args.horaInicio,
			minutoInicio: args.minutoInicio,
			dataFim: args.dataFim,
			horaFim: args.horaFim,
			minutoFim: args.minutoFim,
			motivo: args.motivo,
			isento: args.isento,
			ativo: true,
			criadoEm: Date.now()
		});

		return { success: true, dispensaId };
	}
});

/**
 * Remove uma dispensa de registro (cancela)
 */
export const removerDispensaRegistro = mutation({
	args: {
		dispensaId: v.id('dispensasRegistro')
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		const dispensa = await ctx.db.get(args.dispensaId);
		if (!dispensa) {
			throw new Error('Dispensa não encontrada');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, dispensa.funcionarioId);
		if (!isGestor && dispensa.gestorId !== usuario._id) {
			throw new Error('Você não tem permissão para remover esta dispensa');
		}

		// Desativar dispensa
		await ctx.db.patch(args.dispensaId, {
			ativo: false
		});

		return { success: true };
	}
});

/**
 * Lista dispensas de registro
 */
export const listarDispensas = query({
	args: {
		funcionarioId: v.optional(v.id('funcionarios')),
		apenasAtivas: v.optional(v.boolean())
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		let dispensas;

		if (args.funcionarioId) {
			// Verificar se é gestor do funcionário ou o próprio funcionário
			const funcionarioId = args.funcionarioId; // Garantir que não é undefined
			const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, funcionarioId);
			const isProprioFuncionario = usuario.funcionarioId === funcionarioId;

			if (!isGestor && !isProprioFuncionario) {
				throw new Error('Você não tem permissão para ver estas dispensas');
			}

			dispensas = await ctx.db
				.query('dispensasRegistro')
				.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
				.filter((q) => {
					if (args.apenasAtivas !== undefined && args.apenasAtivas) {
						return q.eq(q.field('ativo'), true);
					}
					return true; // Retornar todas se apenasAtivas não for especificado
				})
				.order('desc')
				.collect();
		} else {
			// Listar dispensas do gestor
			dispensas = await ctx.db
				.query('dispensasRegistro')
				.withIndex('by_gestor', (q) => q.eq('gestorId', usuario._id))
				.filter((q) => {
					if (args.apenasAtivas !== undefined && args.apenasAtivas) {
						return q.eq(q.field('ativo'), true);
					}
					return true; // Retornar todas se apenasAtivas não for especificado
				})
				.order('desc')
				.collect();
		}

		// Buscar informações adicionais
		const dispensasComDetalhes = await Promise.all(
			dispensas.map(async (d) => {
				const funcionario = await ctx.db.get(d.funcionarioId);
				const gestor = await ctx.db.get(d.gestorId);

				// Buscar foto do perfil do funcionário através do usuário associado
				let fotoPerfilUrl: string | null = null;
				if (funcionario) {
					const usuario = await ctx.db
						.query('usuarios')
						.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', funcionario._id))
						.first();
					if (usuario?.fotoPerfil) {
						fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
					}
				}

				// Verificar se expirou (se não for isento)
				let expirada = false;
				if (!d.isento) {
					const agora = new Date();
					const dataFimTimestamp = new Date(
						`${d.dataFim}T${d.horaFim.toString().padStart(2, '0')}:${d.minutoFim.toString().padStart(2, '0')}:00`
					).getTime();
					expirada = agora.getTime() > dataFimTimestamp;
				}

				return {
					...d,
					funcionario: funcionario
						? {
								nome: funcionario.nome,
								matricula: funcionario.matricula
							}
						: null,
					fotoPerfilUrl,
					gestor: gestor
						? {
								nome: gestor.nome
							}
						: null,
					expirada
				};
			})
		);

		return dispensasComDetalhes;
	}
});

/**
 * Obtém estatísticas gerenciais do banco de horas para RH
 */
export const obterEstatisticasBancoHorasGerencial = query({
	args: {
		mes: v.string(), // YYYY-MM
		funcionarioId: v.optional(v.id('funcionarios'))
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar permissão de RH/TI

		// Buscar todos os bancos de horas do mês
		let bancosMensais = await ctx.db
			.query('bancoHorasMensal')
			.withIndex('by_mes', (q) => q.eq('mes', args.mes))
			.collect();

		// Filtrar por funcionário se fornecido
		if (args.funcionarioId) {
			bancosMensais = bancosMensais.filter((b) => b.funcionarioId === args.funcionarioId);
		}

		// Buscar informações dos funcionários
		const funcionariosComDetalhes = await Promise.all(
			bancosMensais.map(async (banco) => {
				const funcionario = await ctx.db.get(banco.funcionarioId);
				return {
					...banco,
					funcionario: funcionario
						? {
								nome: funcionario.nome,
								matricula: funcionario.matricula
							}
						: null
				};
			})
		);

		// Calcular estatísticas gerais
		const totalFuncionarios = funcionariosComDetalhes.length;
		const funcionariosPositivos = funcionariosComDetalhes.filter(
			(f) => f.saldoFinalMinutos >= 0
		).length;
		const funcionariosNegativos = totalFuncionarios - funcionariosPositivos;
		const totalHorasExtras = funcionariosComDetalhes.reduce(
			(acc, f) => acc + f.horasExtras,
			0
		);
		const totalDeficit = funcionariosComDetalhes.reduce((acc, f) => acc + f.horasDeficit, 0);

		return {
			mes: args.mes,
			totalFuncionarios,
			funcionariosPositivos,
			funcionariosNegativos,
			totalHorasExtras,
			totalDeficit,
			funcionarios: funcionariosComDetalhes
		};
	}
});

/**
 * Lista histórico de alterações no banco de horas (homologações e ajustes)
 */
export const listarHistoricoAlteracoesBancoHoras = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		mes: v.optional(v.string()) // YYYY-MM - se fornecido, filtra por mês
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		// Buscar homologações do funcionário
		let homologacoes = await ctx.db
			.query('homologacoesPonto')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId))
			.order('desc')
			.collect();

		// Filtrar por mês se fornecido
		if (args.mes) {
			const mesHomologacao = args.mes;
			homologacoes = homologacoes.filter((h) => {
				const dataHomologacao = new Date(h.criadoEm);
				const mesHomologacaoStr = `${dataHomologacao.getFullYear()}-${String(dataHomologacao.getMonth() + 1).padStart(2, '0')}`;
				return mesHomologacaoStr === mesHomologacao;
			});
		}

		// Buscar informações adicionais
		const historicoComDetalhes = await Promise.all(
			homologacoes.map(async (h) => {
				const gestor = await ctx.db.get(h.gestorId);
				const registro = h.registroId ? await ctx.db.get(h.registroId) : null;

				// Determinar tipo de alteração
				let tipoAlteracao: 'edicao_registro' | 'ajuste_banco' | 'outro' = 'outro';
				if (h.registroId && h.horaAnterior !== undefined) {
					tipoAlteracao = 'edicao_registro';
				} else if (h.tipoAjuste) {
					tipoAlteracao = 'ajuste_banco';
				}

				// Calcular diferença em minutos (se for edição de registro)
				let diferencaMinutos: number | undefined = undefined;
				if (h.horaAnterior !== undefined && h.horaNova !== undefined) {
					const minutosAnterior = h.horaAnterior * 60 + (h.minutoAnterior || 0);
					const minutosNovo = h.horaNova * 60 + (h.minutoNova || 0);
					diferencaMinutos = minutosNovo - minutosAnterior;
				}

				return {
					...h,
					tipoAlteracao,
					diferencaMinutos,
					gestor: gestor
						? {
								nome: gestor.nome
							}
						: null,
					registro: registro
						? {
								data: registro.data,
								tipo: registro.tipo,
								horaAnterior: `${String(h.horaAnterior || 0).padStart(2, '0')}:${String(h.minutoAnterior || 0).padStart(2, '0')}`,
								horaNova: `${String(h.horaNova || 0).padStart(2, '0')}:${String(h.minutoNova || 0).padStart(2, '0')}`
							}
						: null,
					dataFormatada: new Date(h.criadoEm).toLocaleDateString('pt-BR', {
						day: '2-digit',
						month: '2-digit',
						year: 'numeric',
						hour: '2-digit',
						minute: '2-digit'
					})
				};
			})
		);

		return historicoComDetalhes;
	}
});

/**
 * Verifica se funcionário está dispensado de registrar ponto em uma data/hora específica
 */
export const verificarDispensaAtiva = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		data: v.string(), // YYYY-MM-DD
		hora: v.optional(v.number()),
		minuto: v.optional(v.number())
	},
	handler: async (ctx, args) => {
		const dispensas = await ctx.db
			.query('dispensasRegistro')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId))
			.filter((q) => q.eq(q.field('ativo'), true))
			.collect();

		const dataConsulta = new Date(args.data);

		for (const dispensa of dispensas) {
			// Se for isento, sempre está dispensado
			if (dispensa.isento) {
				return {
					dispensado: true,
					dispensa,
					motivo: 'Isento de registro (caso excepcional)'
				};
			}

			// Verificar se está no período
			const dataInicio = new Date(dispensa.dataInicio);
			const dataFim = new Date(dispensa.dataFim);

			// Se a data está dentro do período
			if (dataConsulta >= dataInicio && dataConsulta <= dataFim) {
				// Se hora e minuto foram fornecidos, verificar também
				if (args.hora !== undefined && args.minuto !== undefined) {
					const timestampConsulta = new Date(
						`${args.data}T${args.hora.toString().padStart(2, '0')}:${args.minuto.toString().padStart(2, '0')}:00`
					).getTime();
					const timestampInicio = new Date(
						`${dispensa.dataInicio}T${dispensa.horaInicio.toString().padStart(2, '0')}:${dispensa.minutoInicio.toString().padStart(2, '0')}:00`
					).getTime();
					const timestampFim = new Date(
						`${dispensa.dataFim}T${dispensa.horaFim.toString().padStart(2, '0')}:${dispensa.minutoFim.toString().padStart(2, '0')}:00`
					).getTime();

					if (timestampConsulta >= timestampInicio && timestampConsulta <= timestampFim) {
						return {
							dispensado: true,
							dispensa,
							motivo: dispensa.motivo
						};
					}
				} else {
					// Apenas verificar data
					return {
						dispensado: true,
						dispensa,
						motivo: dispensa.motivo
					};
				}
			}
		}

		return {
			dispensado: false,
			dispensa: null,
			motivo: null
		};
	}
});

// ========== QUERIES E MUTATIONS DO SISTEMA AVANÇADO DE BANCO DE HORAS ==========

/**
 * Obtém banco de horas completo com todos os detalhes e ajustes
 */
export const obterBancoHorasCompleto = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		data: v.string() // YYYY-MM-DD
	},
	returns: v.object({
		bancoHoras: v.union(
			v.object({
				_id: v.id('bancoHoras'),
				funcionarioId: v.id('funcionarios'),
				data: v.string(),
				cargaHorariaDiaria: v.number(),
				horasTrabalhadas: v.number(),
				saldoMinutos: v.number(),
				tipoDia: v.optional(
					v.union(
						v.literal('normal'),
						v.literal('atestado'),
						v.literal('licenca'),
						v.literal('ausencia'),
						v.literal('abonado'),
						v.literal('descontado')
					)
				),
				motivoAbono: v.optional(v.string()),
				ajustes: v.array(
					v.object({
						_id: v.id('ajustesBancoHoras'),
						tipo: v.union(v.literal('abonar'), v.literal('descontar'), v.literal('compensar')),
						valorMinutos: v.number(),
						motivoDescricao: v.optional(v.string()),
						motivoTipo: v.optional(
							v.union(v.literal('atestado'), v.literal('licenca'), v.literal('ausencia'), v.literal('manual'))
						)
					})
				),
				inconsistencias: v.array(
					v.object({
						_id: v.id('inconsistenciasBancoHoras'),
						tipo: v.union(
							v.literal('ponto_com_atestado'),
							v.literal('ponto_com_licenca'),
							v.literal('ponto_com_ausencia'),
							v.literal('registro_duplicado'),
							v.literal('sequencia_invalida'),
							v.literal('saldo_inconsistente')
						),
						descricao: v.string(),
						status: v.union(v.literal('pendente'), v.literal('resolvida'), v.literal('ignorada'))
					})
				)
			}),
			v.null()
		)
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		const bancoHoras = await ctx.db
			.query('bancoHoras')
			.withIndex('by_funcionario_data', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('data', args.data)
			)
			.first();

		if (!bancoHoras) {
			return { bancoHoras: null };
		}

		// Buscar ajustes
		const ajustes = bancoHoras.ajustesIds
			? await Promise.all(bancoHoras.ajustesIds.map((id) => ctx.db.get(id)))
			: [];
		const ajustesFiltrados = ajustes.filter((a): a is NonNullable<typeof a> => a !== null);

		// Buscar inconsistências
		const inconsistencias = bancoHoras.inconsistenciasIds
			? await Promise.all(bancoHoras.inconsistenciasIds.map((id) => ctx.db.get(id)))
			: [];
		const inconsistenciasFiltradas = inconsistencias.filter(
			(i): i is NonNullable<typeof i> => i !== null
		);

		return {
			bancoHoras: {
				_id: bancoHoras._id,
				funcionarioId: bancoHoras.funcionarioId,
				data: bancoHoras.data,
				cargaHorariaDiaria: bancoHoras.cargaHorariaDiaria,
				horasTrabalhadas: bancoHoras.horasTrabalhadas,
				saldoMinutos: bancoHoras.saldoMinutos,
				tipoDia: bancoHoras.tipoDia,
				motivoAbono: bancoHoras.motivoAbono,
				ajustes: ajustesFiltrados.map((a) => ({
					_id: a._id,
					tipo: a.tipo,
					valorMinutos: a.valorMinutos,
					motivoDescricao: a.motivoDescricao,
					motivoTipo: a.motivoTipo
				})),
				inconsistencias: inconsistenciasFiltradas.map((i) => ({
					_id: i._id,
					tipo: i.tipo,
					descricao: i.descricao,
					status: i.status
				}))
			}
		};
	}
});

/**
 * Lista ajustes de banco de horas de um funcionário/período
 */
export const listarAjustesBancoHoras = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		dataInicio: v.optional(v.string()), // YYYY-MM-DD
		dataFim: v.optional(v.string()) // YYYY-MM-DD
	},
	returns: v.array(
		v.object({
			_id: v.id('ajustesBancoHoras'),
			tipo: v.union(v.literal('abonar'), v.literal('descontar'), v.literal('compensar')),
			valorMinutos: v.number(),
			motivoTipo: v.optional(
				v.union(v.literal('atestado'), v.literal('licenca'), v.literal('ausencia'), v.literal('manual'))
			),
			motivoDescricao: v.optional(v.string()),
			dataAplicacao: v.string(),
			aplicado: v.boolean(),
			gestor: v.union(
				v.object({
					nome: v.string()
				}),
				v.null()
			)
		})
	),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		let query = ctx.db
			.query('ajustesBancoHoras')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId));

		// Filtrar por período se fornecido
		if (args.dataInicio || args.dataFim) {
			query = query.filter((q) => {
				const data = q.field('dataAplicacao');
				if (args.dataInicio && args.dataFim) {
					return q.and(q.gte(data, args.dataInicio), q.lte(data, args.dataFim));
				} else if (args.dataInicio) {
					return q.gte(data, args.dataInicio);
				} else if (args.dataFim) {
					return q.lte(data, args.dataFim);
				}
				return true;
			});
		}

		const ajustes = await query.order('desc').collect();

		// Buscar informações dos gestores
		const ajustesComDetalhes = await Promise.all(
			ajustes.map(async (ajuste) => {
				const gestor = ajuste.gestorId ? await ctx.db.get(ajuste.gestorId) : null;
				return {
					_id: ajuste._id,
					tipo: ajuste.tipo,
					valorMinutos: ajuste.valorMinutos,
					motivoTipo: ajuste.motivoTipo,
					motivoDescricao: ajuste.motivoDescricao,
					dataAplicacao: ajuste.dataAplicacao,
					aplicado: ajuste.aplicado,
					gestor: gestor ? { nome: gestor.nome } : null
				};
			})
		);

		return ajustesComDetalhes;
	}
});

/**
 * Lista inconsistências detectadas
 */
export const listarInconsistenciasBancoHoras = query({
	args: {
		funcionarioId: v.optional(v.id('funcionarios')),
		status: v.optional(v.union(v.literal('pendente'), v.literal('resolvida'), v.literal('ignorada')))
	},
	returns: v.array(
		v.object({
			_id: v.id('inconsistenciasBancoHoras'),
			funcionarioId: v.id('funcionarios'),
			tipo: v.union(
				v.literal('ponto_com_atestado'),
				v.literal('ponto_com_licenca'),
				v.literal('ponto_com_ausencia'),
				v.literal('registro_duplicado'),
				v.literal('sequencia_invalida'),
				v.literal('saldo_inconsistente')
			),
			descricao: v.string(),
			dataDetectada: v.string(),
			dataInconsistencia: v.string(),
			status: v.union(v.literal('pendente'), v.literal('resolvida'), v.literal('ignorada')),
			funcionario: v.union(
				v.object({
					nome: v.string(),
					matricula: v.optional(v.string())
				}),
				v.null()
			)
		})
	),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		let inconsistencias: Doc<'inconsistenciasBancoHoras'>[];

		// Usar índice composto se ambos os filtros estiverem presentes
		if (args.funcionarioId && args.status) {
			const funcionarioId = args.funcionarioId;
			const status = args.status;
			inconsistencias = await ctx.db
				.query('inconsistenciasBancoHoras')
				.withIndex('by_funcionario_status', (q) =>
					q.eq('funcionarioId', funcionarioId).eq('status', status)
				)
				.order('desc')
				.collect();
		} else if (args.funcionarioId) {
			// Filtrar apenas por funcionário
			const funcionarioId = args.funcionarioId;
			inconsistencias = await ctx.db
				.query('inconsistenciasBancoHoras')
				.withIndex('by_funcionario', (q) => q.eq('funcionarioId', funcionarioId))
				.order('desc')
				.collect();
		} else if (args.status) {
			// Filtrar apenas por status
			const status = args.status;
			inconsistencias = await ctx.db
				.query('inconsistenciasBancoHoras')
				.withIndex('by_status', (q) => q.eq('status', status))
				.order('desc')
				.collect();
		} else {
			// Sem filtros, usar full table scan e ordenar manualmente
			inconsistencias = await ctx.db.query('inconsistenciasBancoHoras').collect();
			// Ordenar por data de detecção (mais recente primeiro)
			inconsistencias = inconsistencias.sort((a, b) => {
				const dataA = new Date(a.dataDetectada).getTime();
				const dataB = new Date(b.dataDetectada).getTime();
				return dataB - dataA; // Descendente
			});
		}

		// Buscar informações dos funcionários
		const inconsistenciasComDetalhes = await Promise.all(
			inconsistencias.map(async (inconsistencia: Doc<'inconsistenciasBancoHoras'>) => {
				const funcionario = (await ctx.db.get(inconsistencia.funcionarioId)) as Doc<'funcionarios'> | null;
				return {
					_id: inconsistencia._id,
					funcionarioId: inconsistencia.funcionarioId,
					tipo: inconsistencia.tipo,
					descricao: inconsistencia.descricao,
					dataDetectada: inconsistencia.dataDetectada,
					dataInconsistencia: inconsistencia.dataInconsistencia,
					status: inconsistencia.status,
					funcionario: funcionario
						? {
								nome: funcionario.nome,
								matricula: funcionario.matricula
							}
						: null
				};
			})
		);

		return inconsistenciasComDetalhes;
	}
});

/**
 * Obtém configurações do sistema de banco de horas
 */
export const obterConfiguracaoBancoHoras = query({
	args: {},
	returns: v.union(
		v.object({
			limiteSaldoPositivoMinutos: v.optional(v.number()),
			limiteSaldoNegativoMinutos: v.optional(v.number()),
			considerarAjustesAutomaticos: v.optional(v.boolean()),
			periodicidadeVerificacao: v.optional(v.union(v.literal('diario'), v.literal('semanal'), v.literal('mensal')))
		}),
		v.null()
	),
	handler: async (ctx) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar permissão de TI

		const config = await ctx.db
			.query('configuracaoBancoHoras')
			.order('desc')
			.first();

		if (!config) {
			return null;
		}

		return {
			limiteSaldoPositivoMinutos: config.limiteSaldoPositivoMinutos,
			limiteSaldoNegativoMinutos: config.limiteSaldoNegativoMinutos,
			considerarAjustesAutomaticos: config.considerarAjustesAutomaticos,
			periodicidadeVerificacao: config.periodicidadeVerificacao
		};
	}
});

/**
 * Obtém alertas configurados
 */
export const obterAlertasConfigurados = query({
	args: {},
	returns: v.array(
		v.object({
			_id: v.id('alertasBancoHoras'),
			tipoAlerta: v.union(
				v.literal('saldo_negativo'),
				v.literal('saldo_negativo_critico'),
				v.literal('inconsistencia_detectada'),
				v.literal('dias_sem_registro'),
				v.literal('limite_saldo_excedido')
			),
			periodicidade: v.union(v.literal('diario'), v.literal('semanal'), v.literal('mensal')),
			enviarEmail: v.boolean(),
			enviarChat: v.boolean(),
			destinatariosEmail: v.optional(v.array(v.id('usuarios'))),
			destinatariosChat: v.optional(v.array(v.id('usuarios'))),
			threshold: v.optional(v.number()),
			limiteMinutos: v.optional(v.number()),
			ativo: v.boolean()
		})
	),
	handler: async (ctx) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar permissão de TI

		// Retornar todos os alertas (ativos e inativos) para permitir edição
		const alertas = await ctx.db.query('alertasBancoHoras').collect();

		return alertas.map((a) => ({
			_id: a._id,
			tipoAlerta: a.tipoAlerta,
			periodicidade: a.periodicidade,
			enviarEmail: a.enviarEmail,
			enviarChat: a.enviarChat,
			destinatariosEmail: a.destinatariosEmail || [],
			destinatariosChat: a.destinatariosChat || [],
			threshold: a.threshold,
			limiteMinutos: a.limiteMinutos,
			ativo: a.ativo
		}));
	}
});

/**
 * Verifica inconsistências para um funcionário/período
 */
export const verificarInconsistencias = query({
	args: {
		funcionarioId: v.id('funcionarios'),
		dataInicio: v.optional(v.string()),
		dataFim: v.optional(v.string())
	},
	returns: v.array(
		v.object({
			_id: v.id('inconsistenciasBancoHoras'),
			tipo: v.union(
				v.literal('ponto_com_atestado'),
				v.literal('ponto_com_licenca'),
				v.literal('ponto_com_ausencia'),
				v.literal('registro_duplicado'),
				v.literal('sequencia_invalida'),
				v.literal('saldo_inconsistente')
			),
			descricao: v.string(),
			dataDetectada: v.string(),
			status: v.union(v.literal('pendente'), v.literal('resolvida'), v.literal('ignorada'))
		})
	),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é o próprio funcionário ou tem permissão
		if (usuario.funcionarioId !== args.funcionarioId) {
			// TODO: Verificar permissão de RH
		}

		let query = ctx.db
			.query('inconsistenciasBancoHoras')
			.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId));

		// Filtrar por período se fornecido
		if (args.dataInicio || args.dataFim) {
			query = query.filter((q) => {
				const data = q.field('dataDetectada');
				if (args.dataInicio && args.dataFim) {
					return q.and(q.gte(data, args.dataInicio), q.lte(data, args.dataFim));
				} else if (args.dataInicio) {
					return q.gte(data, args.dataInicio);
				} else if (args.dataFim) {
					return q.lte(data, args.dataFim);
				}
				return true;
			});
		}

		const inconsistencias = await query.order('desc').collect();

		return inconsistencias.map((i) => ({
			_id: i._id,
			tipo: i.tipo,
			descricao: i.descricao,
			dataDetectada: i.dataDetectada,
			status: i.status
		}));
	}
});

/**
 * Cria ajuste manual de banco de horas (abonar/descontar)
 */
export const criarAjusteBancoHoras = mutation({
	args: {
		funcionarioId: v.id('funcionarios'),
		tipo: v.union(v.literal('abonar'), v.literal('descontar'), v.literal('compensar')),
		valorHoras: v.number(),
		valorMinutos: v.number(),
		dataAplicacao: v.string(), // YYYY-MM-DD
		motivoDescricao: v.string(),
		observacoes: v.optional(v.string())
	},
	returns: v.object({
		ajusteId: v.id('ajustesBancoHoras'),
		success: v.boolean()
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, args.funcionarioId);
		if (!isGestor) {
			throw new Error('Você não tem permissão para criar ajuste de banco de horas para este funcionário');
		}

		// Calcular valor total em minutos
		const valorTotalMinutos = args.valorHoras * 60 + args.valorMinutos;
		const valorFinal = args.tipo === 'descontar' ? -valorTotalMinutos : valorTotalMinutos;

		// Criar ajuste
		const ajusteId = await ctx.db.insert('ajustesBancoHoras', {
			funcionarioId: args.funcionarioId,
			tipo: args.tipo,
			motivoTipo: 'manual',
			motivoDescricao: args.motivoDescricao,
			valorMinutos: valorFinal,
			dataAplicacao: args.dataAplicacao,
			gestorId: usuario._id,
			observacoes: args.observacoes,
			aplicado: false, // Será aplicado no próximo recálculo
			criadoEm: Date.now()
		});

		// Aplicar ajuste imediatamente
		const bancoHorasAtual = await ctx.db
			.query('bancoHoras')
			.withIndex('by_funcionario_data', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('data', args.dataAplicacao)
			)
			.first();

		if (bancoHorasAtual) {
			// Atualizar saldo do dia
			await ctx.db.patch(bancoHorasAtual._id, {
				saldoMinutos: bancoHorasAtual.saldoMinutos + valorFinal,
				ajustesIds: [...(bancoHorasAtual.ajustesIds || []), ajusteId]
			});
		} else {
			// Criar novo registro de banco de horas para o dia
			const config = await ctx.db
				.query('configuracaoPonto')
				.withIndex('by_ativo', (q) => q.eq('ativo', true))
				.first();

			if (config) {
				const cargaHorariaDiaria = calcularCargaHorariaDiaria(config);
				await ctx.db.insert('bancoHoras', {
					funcionarioId: args.funcionarioId,
					data: args.dataAplicacao,
					cargaHorariaDiaria,
					horasTrabalhadas: 0,
					saldoMinutos: valorFinal,
					registrosPontoIds: [],
					ajustesIds: [ajusteId],
					tipoDia: args.tipo === 'abonar' ? 'abonado' : 'descontado',
					motivoAbono: args.motivoDescricao,
					calculadoEm: Date.now()
				});
			}
		}

		// Marcar ajuste como aplicado
		await ctx.db.patch(ajusteId, {
			aplicado: true,
			aplicadoEm: Date.now()
		});

		// Recalcular banco de horas mensal
		const mes = args.dataAplicacao.substring(0, 7);
		await calcularBancoHorasMensal(ctx, args.funcionarioId, mes);

		return { ajusteId, success: true };
	}
});

/**
 * Resolve uma inconsistência
 */
export const resolverInconsistencia = mutation({
	args: {
		inconsistenciaId: v.id('inconsistenciasBancoHoras'),
		resolucao: v.string()
	},
	returns: v.object({
		success: v.boolean()
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		const inconsistencia = await ctx.db.get(args.inconsistenciaId);
		if (!inconsistencia) {
			throw new Error('Inconsistência não encontrada');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, inconsistencia.funcionarioId);
		if (!isGestor) {
			throw new Error('Você não tem permissão para resolver esta inconsistência');
		}

		// Atualizar inconsistência
		await ctx.db.patch(args.inconsistenciaId, {
			status: 'resolvida',
			resolucao: args.resolucao,
			resolvidoPor: usuario._id,
			resolvidoEm: Date.now()
		});

		return { success: true };
	}
});

/**
 * Atualiza configurações gerais do banco de horas
 */
export const atualizarConfiguracaoBancoHoras = mutation({
	args: {
		limiteSaldoPositivoMinutos: v.optional(v.number()),
		limiteSaldoNegativoMinutos: v.optional(v.number()),
		considerarAjustesAutomaticos: v.optional(v.boolean()),
		periodicidadeVerificacao: v.optional(v.union(v.literal('diario'), v.literal('semanal'), v.literal('mensal')))
	},
	returns: v.object({
		success: v.boolean(),
		configId: v.id('configuracaoBancoHoras')
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar permissão de TI

		// Buscar configuração existente ou criar nova
		const configExistente = await ctx.db
			.query('configuracaoBancoHoras')
			.order('desc')
			.first();

		if (configExistente) {
			await ctx.db.patch(configExistente._id, {
				limiteSaldoPositivoMinutos: args.limiteSaldoPositivoMinutos,
				limiteSaldoNegativoMinutos: args.limiteSaldoNegativoMinutos,
				considerarAjustesAutomaticos: args.considerarAjustesAutomaticos,
				periodicidadeVerificacao: args.periodicidadeVerificacao,
				atualizadoPor: usuario._id,
				atualizadoEm: Date.now()
			});
			return { success: true, configId: configExistente._id };
		} else {
			const configId = await ctx.db.insert('configuracaoBancoHoras', {
				limiteSaldoPositivoMinutos: args.limiteSaldoPositivoMinutos,
				limiteSaldoNegativoMinutos: args.limiteSaldoNegativoMinutos,
				considerarAjustesAutomaticos: args.considerarAjustesAutomaticos ?? true,
				periodicidadeVerificacao: args.periodicidadeVerificacao,
				atualizadoPor: usuario._id,
				atualizadoEm: Date.now()
			});
			return { success: true, configId };
		}
	}
});

/**
 * Atualiza configuração de alerta específico
 */
export const criarAlertaBancoHoras = mutation({
	args: {
		tipoAlerta: v.union(
			v.literal('saldo_negativo'),
			v.literal('saldo_negativo_critico'),
			v.literal('inconsistencia_detectada'),
			v.literal('dias_sem_registro'),
			v.literal('limite_saldo_excedido')
		),
		periodicidade: v.union(v.literal('diario'), v.literal('semanal'), v.literal('mensal')),
		enviarEmail: v.boolean(),
		enviarChat: v.boolean(),
		destinatariosEmail: v.optional(v.array(v.id('usuarios'))),
		destinatariosChat: v.optional(v.array(v.id('usuarios'))),
		threshold: v.optional(v.number()),
		limiteMinutos: v.optional(v.number()),
		ativo: v.boolean()
	},
	returns: v.object({
		success: v.boolean(),
		alertaId: v.id('alertasBancoHoras')
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar permissão de TI

		// Verificar se já existe alerta do mesmo tipo
		const alertaExistente = await ctx.db
			.query('alertasBancoHoras')
			.withIndex('by_tipo', (q) => q.eq('tipoAlerta', args.tipoAlerta))
			.first();

		if (alertaExistente) {
			throw new Error('Já existe um alerta configurado para este tipo');
		}

		const alertaId = await ctx.db.insert('alertasBancoHoras', {
			tipoAlerta: args.tipoAlerta,
			periodicidade: args.periodicidade,
			enviarEmail: args.enviarEmail,
			enviarChat: args.enviarChat,
			destinatariosEmail: args.destinatariosEmail || [],
			destinatariosChat: args.destinatariosChat || [],
			threshold: args.threshold,
			limiteMinutos: args.limiteMinutos,
			ativo: args.ativo,
			criadoPor: usuario._id,
			criadoEm: Date.now()
		});

		return { success: true, alertaId };
	}
});

export const atualizarConfiguracaoAlerta = mutation({
	args: {
		alertaId: v.id('alertasBancoHoras'),
		periodicidade: v.optional(v.union(v.literal('diario'), v.literal('semanal'), v.literal('mensal'))),
		enviarEmail: v.optional(v.boolean()),
		enviarChat: v.optional(v.boolean()),
		destinatariosEmail: v.optional(v.array(v.id('usuarios'))),
		destinatariosChat: v.optional(v.array(v.id('usuarios'))),
		threshold: v.optional(v.number()),
		limiteMinutos: v.optional(v.number()),
		ativo: v.optional(v.boolean())
	},
	returns: v.object({
		success: v.boolean()
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar permissão de TI

		const alerta = await ctx.db.get(args.alertaId);
		if (!alerta) {
			throw new Error('Alerta não encontrado');
		}

		await ctx.db.patch(args.alertaId, {
			periodicidade: args.periodicidade ?? alerta.periodicidade,
			enviarEmail: args.enviarEmail ?? alerta.enviarEmail,
			enviarChat: args.enviarChat ?? alerta.enviarChat,
			destinatariosEmail: args.destinatariosEmail !== undefined ? args.destinatariosEmail : alerta.destinatariosEmail,
			destinatariosChat: args.destinatariosChat !== undefined ? args.destinatariosChat : alerta.destinatariosChat,
			threshold: args.threshold,
			limiteMinutos: args.limiteMinutos,
			ativo: args.ativo ?? alerta.ativo,
			atualizadoPor: usuario._id,
			atualizadoEm: Date.now()
		});

		return { success: true };
	}
});

/**
 * Recalcula banco de horas para um funcionário/período
 */
export const recalcularBancoHoras = mutation({
	args: {
		funcionarioId: v.id('funcionarios'),
		dataInicio: v.string(), // YYYY-MM-DD
		dataFim: v.string() // YYYY-MM-DD
	},
	returns: v.object({
		success: v.boolean(),
		diasRecalculados: v.number()
	}),
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// Verificar se é gestor do funcionário
		const isGestor = await verificarGestorDoFuncionario(ctx, usuario._id, args.funcionarioId);
		if (!isGestor) {
			throw new Error('Você não tem permissão para recalcular banco de horas deste funcionário');
		}

		// Buscar configuração de ponto
		const config = await ctx.db
			.query('configuracaoPonto')
			.withIndex('by_ativo', (q) => q.eq('ativo', true))
			.first();

		if (!config) {
			throw new Error('Configuração de ponto não encontrada');
		}

		// Gerar todas as datas do período
		const dataInicioObj = new Date(args.dataInicio);
		const dataFimObj = new Date(args.dataFim);
		const datas: string[] = [];
		const dataAtual = new Date(dataInicioObj);

		while (dataAtual <= dataFimObj) {
			const ano = dataAtual.getFullYear();
			const mes = String(dataAtual.getMonth() + 1).padStart(2, '0');
			const dia = String(dataAtual.getDate()).padStart(2, '0');
			datas.push(`${ano}-${mes}-${dia}`);
			dataAtual.setDate(dataAtual.getDate() + 1);
		}

		// Recalcular para cada data
		let diasRecalculados = 0;
		for (const data of datas) {
			await atualizarBancoHoras(ctx, args.funcionarioId, data, {
				horarioEntrada: config.horarioEntrada,
				horarioSaidaAlmoco: config.horarioSaidaAlmoco,
				horarioRetornoAlmoco: config.horarioRetornoAlmoco,
				horarioSaida: config.horarioSaida
			});
			diasRecalculados++;
		}

		return { success: true, diasRecalculados };
	}
});

/**
 * Mutation interna para recalcular banco de horas de uma data específica
 */
export const recalcularBancoHorasData = internalMutation({
	args: {
		funcionarioId: v.id('funcionarios'),
		data: v.string() // YYYY-MM-DD
	},
	returns: v.null(),
	handler: async (ctx, args) => {
		// Buscar configuração de ponto
		const config = await ctx.db
			.query('configuracaoPonto')
			.withIndex('by_ativo', (q) => q.eq('ativo', true))
			.first();

		if (!config) {
			return null;
		}

		// Recalcular banco de horas para a data
		await atualizarBancoHoras(ctx, args.funcionarioId, args.data, {
			horarioEntrada: config.horarioEntrada,
			horarioSaidaAlmoco: config.horarioSaidaAlmoco,
			horarioRetornoAlmoco: config.horarioRetornoAlmoco,
			horarioSaida: config.horarioSaida
		});

		return null;
	}
});

// ========== SISTEMA DE ALERTAS DE BANCO DE HORAS ==========

/**
 * Helper: Encontrar gestor do funcionário
 */
async function encontrarGestorDoFuncionarioParaAlerta(
	ctx: QueryCtx | MutationCtx,
	funcionarioId: Id<'funcionarios'>
): Promise<Id<'usuarios'> | null> {
	const funcionario = await ctx.db.get(funcionarioId);
	if (!funcionario || !funcionario.gestorId) {
		return null;
	}
	return funcionario.gestorId;
}

/**
 * Helper: Enviar alerta via chat
 */
async function enviarAlertaChat(
	ctx: MutationCtx,
	gestorId: Id<'usuarios'>,
	titulo: string,
	mensagem: string
): Promise<void> {
	// Criar notificação no sistema
	await ctx.db.insert('notificacoes', {
		usuarioId: gestorId,
		tipo: 'nova_mensagem',
		titulo,
		descricao: mensagem,
		lida: false,
		criadaEm: Date.now()
	});
}

/**
 * Helper: Enviar alerta via email
 */
async function enviarAlertaEmail(
	ctx: MutationCtx,
	gestorId: Id<'usuarios'>,
	titulo: string,
	mensagem: string
): Promise<void> {
	const gestor = await ctx.db.get(gestorId);
	if (!gestor || !gestor.email) {
		return;
	}

	// Obter URL do sistema
	let urlSistema = process.env.FRONTEND_URL || 'http://localhost:5173';
	if (!urlSistema.match(/^https?:\/\//i)) {
		urlSistema = `http://${urlSistema}`;
	}

	// Enviar email usando template
	await ctx.scheduler.runAfter(0, api.email.enviarEmailComTemplate, {
		destinatario: gestor.email,
		destinatarioId: gestorId,
		templateCodigo: 'banco_horas_alerta',
		variaveis: {
			gestorNome: gestor.nome,
			titulo,
			mensagem,
			urlSistema
		},
		enviadoPor: gestorId
	});
}

/**
 * Detecta e envia alertas de banco de horas para um funcionário
 */
export const detectarEEnviarAlertasBancoHoras = internalMutation({
	args: {
		funcionarioId: v.id('funcionarios')
	},
	returns: v.object({
		alertasEnviados: v.number()
	}),
	handler: async (ctx, args) => {
		// Buscar configurações de alertas ativas
		const alertasConfigurados = await ctx.db
			.query('alertasBancoHoras')
			.withIndex('by_ativo', (q) => q.eq('ativo', true))
			.collect();

		if (alertasConfigurados.length === 0) {
			return { alertasEnviados: 0 };
		}

		// Buscar gestor do funcionário
		const gestorId = await encontrarGestorDoFuncionarioParaAlerta(ctx, args.funcionarioId);
		if (!gestorId) {
			return { alertasEnviados: 0 };
		}

		// Buscar banco de horas mensal atual
		const hoje = new Date();
		const mesAtual = `${hoje.getFullYear()}-${String(hoje.getMonth() + 1).padStart(2, '0')}`;

		const bancoMensal = await ctx.db
			.query('bancoHorasMensal')
			.withIndex('by_funcionario_mes', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('mes', mesAtual)
			)
			.first();

		// Buscar inconsistências pendentes
		const inconsistenciasPendentes = await ctx.db
			.query('inconsistenciasBancoHoras')
			.withIndex('by_funcionario_status', (q) =>
				q.eq('funcionarioId', args.funcionarioId).eq('status', 'pendente')
			)
			.collect();

		let alertasEnviados = 0;

		// Verificar cada tipo de alerta configurado
		for (const alertaConfig of alertasConfigurados) {
			let deveEnviar = false;
			let titulo = '';
			let mensagem = '';

			switch (alertaConfig.tipoAlerta) {
				case 'saldo_negativo':
					if (bancoMensal && bancoMensal.saldoFinalMinutos < 0) {
						const horasNegativas = Math.abs(bancoMensal.saldoFinalMinutos) / 60;
						if (!alertaConfig.limiteMinutos || Math.abs(bancoMensal.saldoFinalMinutos) >= alertaConfig.limiteMinutos) {
							deveEnviar = true;
							titulo = '⚠️ Alerta: Saldo Negativo de Banco de Horas';
							mensagem = `O funcionário possui saldo negativo acumulado de ${Math.floor(horasNegativas)}h ${Math.abs(bancoMensal.saldoFinalMinutos) % 60}min.`;
						}
					}
					break;

				case 'saldo_negativo_critico':
					if (bancoMensal && bancoMensal.saldoFinalMinutos < 0) {
						const horasNegativas = Math.abs(bancoMensal.saldoFinalMinutos) / 60;
						if (horasNegativas >= 8) {
							deveEnviar = true;
							titulo = '🚨 Alerta Crítico: Saldo Negativo Crítico de Banco de Horas';
							mensagem = `O funcionário possui saldo negativo crítico acumulado de ${Math.floor(horasNegativas)}h ${Math.abs(bancoMensal.saldoFinalMinutos) % 60}min. Ação imediata necessária.`;
						}
					}
					break;

				case 'inconsistencia_detectada':
					if (inconsistenciasPendentes.length > 0) {
						deveEnviar = true;
						titulo = '⚠️ Alerta: Inconsistências Detectadas no Banco de Horas';
						mensagem = `Foram detectadas ${inconsistenciasPendentes.length} inconsistência(s) no banco de horas do funcionário que precisam ser resolvidas.`;
					}
					break;

				case 'dias_sem_registro':
					// Verificar últimos 7 dias
					const ultimos7Dias: string[] = [];
					for (let i = 0; i < 7; i++) {
						const data = new Date();
						data.setDate(data.getDate() - i);
						ultimos7Dias.push(data.toISOString().split('T')[0]!);
					}

					const registrosRecentes = await ctx.db
						.query('bancoHoras')
						.withIndex('by_funcionario', (q) => q.eq('funcionarioId', args.funcionarioId))
						.filter((q) => {
							const data = q.field('data');
							return q.or(...ultimos7Dias.map((dia) => q.eq(data, dia)));
						})
						.collect();

					const diasComRegistro = new Set(registrosRecentes.map((r) => r.data));
					const diasSemRegistro = ultimos7Dias.filter((dia) => !diasComRegistro.has(dia));

					if (diasSemRegistro.length >= 3) {
						deveEnviar = true;
						titulo = '⚠️ Alerta: Múltiplos Dias Sem Registro de Ponto';
						mensagem = `O funcionário não possui registro de ponto em ${diasSemRegistro.length} dos últimos 7 dias.`;
					}
					break;

				case 'limite_saldo_excedido':
					if (bancoMensal) {
						const config = await ctx.db
							.query('configuracaoBancoHoras')
							.order('desc')
							.first();

						if (config) {
							if (
								config.limiteSaldoPositivoMinutos &&
								bancoMensal.saldoFinalMinutos > config.limiteSaldoPositivoMinutos
							) {
								deveEnviar = true;
								titulo = '⚠️ Alerta: Limite de Saldo Positivo Excedido';
								mensagem = `O funcionário excedeu o limite de saldo positivo configurado.`;
							} else if (
								config.limiteSaldoNegativoMinutos &&
								Math.abs(bancoMensal.saldoFinalMinutos) > config.limiteSaldoNegativoMinutos
							) {
								deveEnviar = true;
								titulo = '⚠️ Alerta: Limite de Saldo Negativo Excedido';
								mensagem = `O funcionário excedeu o limite de saldo negativo configurado.`;
							}
						}
					}
					break;
			}

			if (deveEnviar && (alertaConfig.enviarChat || alertaConfig.enviarEmail)) {
				// Determinar destinatários: usar específicos se configurados, senão usar gestor padrão
				const destinatariosChat = alertaConfig.destinatariosChat && alertaConfig.destinatariosChat.length > 0
					? alertaConfig.destinatariosChat
					: gestorId ? [gestorId] : [];
				
				const destinatariosEmail = alertaConfig.destinatariosEmail && alertaConfig.destinatariosEmail.length > 0
					? alertaConfig.destinatariosEmail
					: gestorId ? [gestorId] : [];

				// Enviar para destinatários de chat
				if (alertaConfig.enviarChat && destinatariosChat.length > 0) {
					for (const destinatarioId of destinatariosChat) {
						await enviarAlertaChat(ctx, destinatarioId, titulo, mensagem);
					}
				}

				// Enviar para destinatários de email
				if (alertaConfig.enviarEmail && destinatariosEmail.length > 0) {
					for (const destinatarioId of destinatariosEmail) {
						await enviarAlertaEmail(ctx, destinatarioId, titulo, mensagem);
					}
				}

				if (destinatariosChat.length > 0 || destinatariosEmail.length > 0) {
					alertasEnviados++;
				}
			}
		}

		return { alertasEnviados };
	}
});

/**
 * Action interna para processar alertas de banco de horas (chamada por cron)
 */
export const processarAlertasBancoHoras = internalMutation({
	args: {},
	returns: v.object({
		funcionariosProcessados: v.number(),
		alertasEnviados: v.number()
	}),
	handler: async (ctx) => {
		// Buscar todos os funcionários ativos
		const funcionarios = await ctx.db
			.query('funcionarios')
			.filter((q) => q.eq(q.field('desligamentoData'), undefined))
			.collect();

		let totalAlertasEnviados = 0;

		for (const funcionario of funcionarios) {
			const resultado = await ctx.runMutation(internal.pontos.detectarEEnviarAlertasBancoHoras, {
				funcionarioId: funcionario._id
			});
			totalAlertasEnviados += resultado.alertasEnviados;
		}

		return {
			funcionariosProcessados: funcionarios.length,
			alertasEnviados: totalAlertasEnviados
		};
	}
});

/**
 * Inicializa alertas padrão do sistema (chamada uma vez)
 */
export const inicializarAlertasPadrao = mutation({
	args: {},
	returns: v.object({
		success: v.boolean(),
		alertasCriados: v.number()
	}),
	handler: async (ctx) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar permissão de TI

		// Verificar se já existem alertas
		const alertasExistentes = await ctx.db.query('alertasBancoHoras').collect();
		if (alertasExistentes.length > 0) {
			return { success: true, alertasCriados: 0 };
		}

		// Criar alertas padrão
		const tiposAlerta: Array<{
			tipoAlerta: 'saldo_negativo' | 'saldo_negativo_critico' | 'inconsistencia_detectada' | 'dias_sem_registro' | 'limite_saldo_excedido';
			periodicidade: 'diario' | 'semanal' | 'mensal';
			enviarEmail: boolean;
			enviarChat: boolean;
			limiteMinutos?: number;
		}> = [
			{
				tipoAlerta: 'saldo_negativo',
				periodicidade: 'diario',
				enviarEmail: true,
				enviarChat: true,
				limiteMinutos: 60 // 1 hora
			},
			{
				tipoAlerta: 'saldo_negativo_critico',
				periodicidade: 'diario',
				enviarEmail: true,
				enviarChat: true
			},
			{
				tipoAlerta: 'inconsistencia_detectada',
				periodicidade: 'diario',
				enviarEmail: true,
				enviarChat: true
			},
			{
				tipoAlerta: 'dias_sem_registro',
				periodicidade: 'semanal',
				enviarEmail: true,
				enviarChat: true
			},
			{
				tipoAlerta: 'limite_saldo_excedido',
				periodicidade: 'diario',
				enviarEmail: true,
				enviarChat: true
			}
		];

		let alertasCriados = 0;
		for (const tipoAlerta of tiposAlerta) {
			await ctx.db.insert('alertasBancoHoras', {
				...tipoAlerta,
				ativo: true,
				criadoPor: usuario._id,
				criadoEm: Date.now()
			});
			alertasCriados++;
		}

		return { success: true, alertasCriados };
	}
});
