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

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

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

		// 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
 */
function calcularHorasTrabalhadas(
	registros: Array<{
		tipo: string;
		hora: number;
		minuto: number;
	}>
): number {
	// 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;
	});

	let horasTrabalhadas = 0;

	// Procurar entrada e saída
	const entrada = registrosOrdenados.find((r) => r.tipo === 'entrada');
	const saida = registrosOrdenados.find((r) => r.tipo === 'saida');

	if (entrada && saida) {
		const minutosEntrada = entrada.hora * 60 + entrada.minuto;
		const minutosSaida = saida.hora * 60 + saida.minuto;

		// Procurar saída e retorno do almoço
		const saidaAlmoco = registrosOrdenados.find((r) => r.tipo === 'saida_almoco');
		const retornoAlmoco = registrosOrdenados.find((r) => r.tipo === 'retorno_almoco');

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

			const horasManha = minutosSaidaAlmoco - minutosEntrada;
			const horasTarde = minutosSaida - minutosRetornoAlmoco;
			horasTrabalhadas = horasManha + horasTarde;
		} else {
			// Sem intervalo de almoço registrado: saída - entrada
			horasTrabalhadas = minutosSaida - minutosEntrada;
		}
	}

	return horasTrabalhadas;
}

/**
 * Atualiza ou cria registro de banco de horas para o dia
 */
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);

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

	// Calcular saldo (positivo = horas extras, negativo = déficit)
	const saldoMinutos = horasTrabalhadas - cargaHorariaDiaria;

	// 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();

	const registrosPontoIds = registrosDoDia.map((r) => r._id);

	if (bancoHorasExistente) {
		// Atualizar existente
		await ctx.db.patch(bancoHorasExistente._id, {
			cargaHorariaDiaria,
			horasTrabalhadas,
			saldoMinutos,
			registrosPontoIds,
			calculadoEm: Date.now()
		});
	} else {
		// Criar novo
		await ctx.db.insert('bancoHoras', {
			funcionarioId,
			data,
			cargaHorariaDiaria,
			horasTrabalhadas,
			saldoMinutos,
			registrosPontoIds,
			calculadoEm: Date.now()
		});
	}
}

/**
 * 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
		};
	}
});

/**
 * 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
		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())
	},
	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]!;
		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
			await ctx.db.patch(bancoHorasAtual._id, {
				saldoMinutos: bancoHorasAtual.saldoMinutos + ajusteFinal
			});
		} 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: [],
				calculadoEm: Date.now()
			});
		}

		// Criar registro de homologação
		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, 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;
	}
});

/**
 * 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
		};
	}
});
