import { v } from 'convex/values';
import { action, internalMutation, internalQuery, mutation, query } from './_generated/server';
import { getCurrentUserFunction } from './auth';
import type { Id } from './_generated/dataModel';
import { api, internal } from './_generated/api';

/**
 * Tipo de retorno da configuração do relógio
 */
type ConfiguracaoRelogioRetorno = {
	servidorNTP?: string | undefined;
	portaNTP?: number | undefined;
	usarServidorExterno: boolean;
	fallbackParaPC: boolean;
	ultimaSincronizacao: number | null;
	offsetSegundos: number | null;
	gmtOffset: number;
};

/**
 * Obtém a configuração do relógio
 */
export const obterConfiguracao = query({
	args: {},
	handler: async (ctx): Promise<ConfiguracaoRelogioRetorno> => {
		// Buscar todas as configurações e pegar a mais recente (por atualizadoEm)
		const configs = await ctx.db.query('configuracaoRelogio').collect();

		// Pegar a configuração mais recente (ordenar por atualizadoEm desc)
		const config =
			configs.length > 0
				? configs.sort((a, b) => (b.atualizadoEm || 0) - (a.atualizadoEm || 0))[0]
				: null;

		if (!config) {
			// Retornar configuração padrão (GMT-3 para Brasília)
			return {
				servidorNTP: 'pool.ntp.org',
				portaNTP: 123,
				usarServidorExterno: false,
				fallbackParaPC: true,
				ultimaSincronizacao: null,
				offsetSegundos: null,
				gmtOffset: -3 // GMT-3 para Brasília
			};
		}

		return {
			...config,
			ultimaSincronizacao: config.ultimaSincronizacao ?? null, // Converter undefined para null
			offsetSegundos: config.offsetSegundos ?? null, // Converter undefined para null
			gmtOffset: config.gmtOffset ?? -3 // Padrão GMT-3 para Brasília se não configurado
		};
	}
});

/**
 * Obtém a configuração do relógio (internal) - usado por actions para evitar referência circular
 */
export const obterConfiguracaoInternal = internalQuery({
	args: {},
	handler: async (ctx): Promise<ConfiguracaoRelogioRetorno> => {
		// Buscar todas as configurações e pegar a mais recente (por atualizadoEm)
		const configs = await ctx.db.query('configuracaoRelogio').collect();

		// Pegar a configuração mais recente (ordenar por atualizadoEm desc)
		const config =
			configs.length > 0
				? configs.sort((a, b) => (b.atualizadoEm || 0) - (a.atualizadoEm || 0))[0]
				: null;

		if (!config) {
			// Retornar configuração padrão (GMT-3 para Brasília)
			return {
				servidorNTP: 'pool.ntp.org',
				portaNTP: 123,
				usarServidorExterno: false,
				fallbackParaPC: true,
				ultimaSincronizacao: null,
				offsetSegundos: null,
				gmtOffset: -3 // GMT-3 para Brasília
			};
		}

		return {
			...config,
			ultimaSincronizacao: config.ultimaSincronizacao ?? null, // Converter undefined para null
			offsetSegundos: config.offsetSegundos ?? null, // Converter undefined para null
			gmtOffset: config.gmtOffset ?? -3 // Padrão GMT-3 para Brasília se não configurado
		};
	}
});

/**
 * Salva configuração do relógio (apenas TI)
 */
export const salvarConfiguracao = mutation({
	args: {
		servidorNTP: v.optional(v.string()),
		portaNTP: v.optional(v.number()),
		usarServidorExterno: v.boolean(),
		fallbackParaPC: v.boolean(),
		gmtOffset: v.optional(v.number())
	},
	handler: async (ctx, args) => {
		const usuario = await getCurrentUserFunction(ctx);
		if (!usuario) {
			throw new Error('Usuário não autenticado');
		}

		// TODO: Verificar se usuário tem permissão de TI

		// Validar servidor NTP se usar servidor externo
		if (args.usarServidorExterno) {
			if (!args.servidorNTP || args.servidorNTP.trim() === '') {
				throw new Error('Servidor NTP é obrigatório quando usar servidor externo');
			}
			if (!args.portaNTP || args.portaNTP < 1 || args.portaNTP > 65535) {
				throw new Error('Porta NTP deve estar entre 1 e 65535');
			}
		}

		// Buscar configuração existente (pegar a mais recente)
		const configs = await ctx.db.query('configuracaoRelogio').collect();

		const configExistente =
			configs.length > 0
				? configs.sort((a, b) => (b.atualizadoEm || 0) - (a.atualizadoEm || 0))[0]
				: null;

		if (configExistente) {
			// Atualizar configuração existente
			await ctx.db.patch(configExistente._id, {
				servidorNTP: args.servidorNTP,
				portaNTP: args.portaNTP,
				usarServidorExterno: args.usarServidorExterno,
				fallbackParaPC: args.fallbackParaPC,
				gmtOffset: args.gmtOffset ?? -3, // Padrão GMT-3 para Brasília
				atualizadoPor: usuario._id as Id<'usuarios'>,
				atualizadoEm: Date.now()
			});
			return { configId: configExistente._id };
		} else {
			// Criar nova configuração
			const configId = await ctx.db.insert('configuracaoRelogio', {
				servidorNTP: args.servidorNTP,
				portaNTP: args.portaNTP,
				usarServidorExterno: args.usarServidorExterno,
				fallbackParaPC: args.fallbackParaPC,
				gmtOffset: args.gmtOffset ?? -3, // Padrão GMT-3 para Brasília
				atualizadoPor: usuario._id as Id<'usuarios'>,
				atualizadoEm: Date.now()
			});
			return { configId };
		}
	}
});

/**
 * Obtém tempo do servidor (timestamp atual)
 */
export const obterTempoServidor = query({
	args: {},
	handler: async () => {
		return {
			timestamp: Date.now(),
			data: new Date().toISOString()
		};
	}
});

/**
 * Tipo de retorno da sincronização
 */
type SincronizacaoRetorno = {
	sucesso: boolean;
	timestamp: number;
	usandoServidorExterno: boolean;
	offsetSegundos: number;
	aviso?: string;
};

/**
 * Sincroniza tempo com servidor NTP (via action)
 * Nota: NTP real requer biblioteca específica, aqui fazemos uma aproximação
 */
export const sincronizarTempo = action({
	args: {},
	handler: async (ctx): Promise<SincronizacaoRetorno> => {
		// Buscar configuração usando query interna para evitar referência circular
		const config: ConfiguracaoRelogioRetorno = await ctx.runQuery(
			internal.configuracaoRelogio.obterConfiguracaoInternal,
			{}
		);

		if (!config.usarServidorExterno) {
			return {
				sucesso: true,
				timestamp: Date.now(),
				usandoServidorExterno: false,
				offsetSegundos: 0
			};
		}

		// Tentar obter tempo de um servidor NTP público via HTTP
		// Nota: NTP real requer protocolo UDP na porta 123, aqui usamos APIs HTTP que retornam UTC
		// O GMT offset será aplicado no frontend
		try {
			const servidorNTP = config.servidorNTP || 'pool.ntp.org';
			let serverTime: number;

			// Se o servidor configurado for uma URL HTTP/HTTPS, tentar usar diretamente
			if (servidorNTP.startsWith('http://') || servidorNTP.startsWith('https://')) {
				try {
					const response = await fetch(servidorNTP);
					if (!response.ok) {
						throw new Error('Falha ao obter tempo do servidor configurado');
					}
					const data = (await response.json()) as {
						unixtime?: number;
						unixTime?: number;
						unixtimestamp?: number;
					};
					// Tentar diferentes formatos de resposta
					if (data.unixtime) {
						serverTime = data.unixtime * 1000; // Converter segundos para milissegundos
					} else if (data.unixTime) {
						serverTime = data.unixTime * 1000;
					} else if (data.unixtimestamp) {
						serverTime = data.unixtimestamp * 1000;
					} else {
						throw new Error('Formato de resposta não reconhecido');
					}
				} catch (error) {
					// Se falhar, tentar APIs genéricas como fallback
					throw new Error(`Falha ao usar servidor configurado: ${error}`);
				}
			} else {
				// Para servidores NTP tradicionais (sem HTTP), usar APIs genéricas que retornam UTC
				// Não usar worldtimeapi.org hardcoded - usar timeapi.io como primeira opção
				try {
					const response = await fetch('https://timeapi.io/api/Time/current/zone?timeZone=UTC');
					if (!response.ok) {
						throw new Error('Falha ao obter tempo');
					}
					const data = (await response.json()) as { unixTime: number };
					serverTime = data.unixTime * 1000;
				} catch {
					// Fallback: usar tempo do servidor Convex (já está em UTC)
					// Não usar worldtimeapi.org como fallback automático
					serverTime = Date.now();
				}
			}

			const localTime = Date.now();
			const offsetSegundos = Math.floor((serverTime - localTime) / 1000);

			// Atualizar configuração com offset
			// Buscar configuração diretamente usando query interna
			const configs = await ctx.runQuery(internal.configuracaoRelogio.listarConfiguracoes, {});
			const configExistente = configs.find(
				(c: { usarServidorExterno: boolean; _id: Id<'configuracaoRelogio'> }) =>
					c.usarServidorExterno === config.usarServidorExterno
			);
			if (configExistente) {
				await ctx.runMutation(internal.configuracaoRelogio.atualizarSincronizacao, {
					configId: configExistente._id,
					offsetSegundos
				});
			}

			return {
				sucesso: true,
				timestamp: serverTime, // Retorna UTC (sem GMT offset aplicado)
				usandoServidorExterno: true,
				offsetSegundos
			};
		} catch (error) {
			// Sempre usar fallback como última opção, mesmo se desabilitado
			// Isso evita que o sistema trave completamente se o servidor externo não estiver disponível
			const aviso: string = config.fallbackParaPC
				? 'Falha ao sincronizar com servidor externo, usando relógio do PC'
				: 'Falha ao sincronizar com servidor externo. Fallback desabilitado, mas usando relógio do PC como última opção.';

			console.warn('Erro ao sincronizar tempo com servidor externo:', error);

			return {
				sucesso: true,
				timestamp: Date.now(),
				usandoServidorExterno: false,
				offsetSegundos: 0,
				aviso
			};
		}
	}
});

/**
 * Lista configurações (internal)
 */
export const listarConfiguracoes = internalQuery({
	args: {},
	handler: async (ctx) => {
		return await ctx.db.query('configuracaoRelogio').collect();
	}
});

/**
 * Atualiza informações de sincronização (internal)
 */
export const atualizarSincronizacao = internalMutation({
	args: {
		configId: v.id('configuracaoRelogio'),
		offsetSegundos: v.number()
	},
	handler: async (ctx, args) => {
		await ctx.db.patch(args.configId, {
			ultimaSincronizacao: Date.now(),
			offsetSegundos: args.offsetSegundos
		});
	}
});
