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

// ========== HELPERS ==========

/**
 * Helper function para obter usuário autenticado
 */
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
	const usuarioAtual = await getCurrentUserFunction(ctx);
	if (!usuarioAtual) {
		console.warn('⚠️ [chamadas] Usuário não autenticado');
	}
	return usuarioAtual || null;
}

/**
 * Gerar nome único para a sala Jitsi
 * Usa configuração do backend se disponível, senão usa padrão 'sgse'
 */
async function gerarRoomName(
	ctx: QueryCtx | MutationCtx,
	conversaId: Id<'conversas'>,
	tipo: 'audio' | 'video'
): Promise<string> {
	// Buscar configuração Jitsi ativa
	const configJitsi = await ctx.db
		.query('configuracaoJitsi')
		.withIndex('by_ativo', (q) => q.eq('ativo', true))
		.first();

	const roomPrefix = configJitsi?.roomPrefix || 'sgse';
	const timestamp = Date.now();
	const random = Math.random().toString(36).substring(2, 9);
	const conversaHash = conversaId
		.replace('conversas|', '')
		.replace(/[^a-zA-Z0-9]/g, '')
		.substring(0, 10);

	return `${roomPrefix}-${tipo}-${conversaHash}-${timestamp}-${random}`;
}

/**
 * Verificar se usuário é anfitrião da chamada (helper interno)
 */
async function verificarSeEhAnfitriao(
	ctx: QueryCtx | MutationCtx,
	chamadaId: Id<'chamadas'>,
	usuarioId: Id<'usuarios'>
): Promise<boolean> {
	const chamada = await ctx.db.get(chamadaId);
	if (!chamada) return false;
	return chamada.criadoPor === usuarioId;
}

/**
 * Verificar se usuário participa da conversa
 */
async function verificarParticipanteConversa(
	ctx: QueryCtx | MutationCtx,
	conversaId: Id<'conversas'>,
	usuarioId: Id<'usuarios'>
): Promise<boolean> {
	const conversa = await ctx.db.get(conversaId);
	if (!conversa) return false;
	return conversa.participantes.includes(usuarioId);
}

// ========== MUTATIONS ==========

/**
 * Criar nova chamada de áudio ou vídeo
 */
export const criarChamada = mutation({
	args: {
		conversaId: v.id('conversas'),
		tipo: v.union(v.literal('audio'), v.literal('video')),
		audioHabilitado: v.optional(v.boolean()),
		videoHabilitado: v.optional(v.boolean())
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		// Verificar se usuário participa da conversa
		const participa = await verificarParticipanteConversa(ctx, args.conversaId, usuarioAtual._id);
		if (!participa) {
			throw new Error('Você não participa desta conversa');
		}

		// Verificar se já existe chamada ativa (aguardando ou em_andamento)
		const todasAtivas = await ctx.db
			.query('chamadas')
			.withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId))
			.filter((q) =>
				q.or(q.eq(q.field('status'), 'aguardando'), q.eq(q.field('status'), 'em_andamento'))
			)
			.collect();

		if (todasAtivas.length > 0) {
			// Retornar chamada ativa existente
			return todasAtivas[0]._id;
		}

		// Obter participantes da conversa
		const conversa = await ctx.db.get(args.conversaId);
		if (!conversa) throw new Error('Conversa não encontrada');

		// Gerar nome único da sala
		const roomName = await gerarRoomName(ctx, args.conversaId, args.tipo);

		// Criar chamada
		const chamadaId = await ctx.db.insert('chamadas', {
			conversaId: args.conversaId,
			tipo: args.tipo,
			roomName,
			criadoPor: usuarioAtual._id,
			participantes: conversa.participantes,
			status: 'aguardando',
			gravando: false,
			configuracoes: {
				audioHabilitado: args.audioHabilitado ?? true,
				videoHabilitado: args.videoHabilitado ?? args.tipo === 'video',
				participantesConfig: conversa.participantes.map((participanteId) => ({
					usuarioId: participanteId,
					audioHabilitado:
						participanteId === usuarioAtual._id ? (args.audioHabilitado ?? true) : true,
					videoHabilitado:
						participanteId === usuarioAtual._id
							? (args.videoHabilitado ?? args.tipo === 'video')
							: args.tipo === 'video',
					forcadoPeloAnfitriao: false
				}))
			},
			criadoEm: Date.now()
		});

		return chamadaId;
	}
});

/**
 * Iniciar chamada (marcar como em andamento)
 */
export const iniciarChamada = mutation({
	args: {
		chamadaId: v.id('chamadas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Verificar se usuário participa da chamada
		if (!chamada.participantes.includes(usuarioAtual._id)) {
			throw new Error('Você não participa desta chamada');
		}

		// Se já estiver em andamento, retornar
		if (chamada.status === 'em_andamento') {
			return null;
		}

		// Atualizar status
		await ctx.db.patch(args.chamadaId, {
			status: 'em_andamento',
			iniciadaEm: Date.now()
		});

		return null;
	}
});

/**
 * Finalizar chamada e calcular duração
 */
export const finalizarChamada = mutation({
	args: {
		chamadaId: v.id('chamadas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Verificar se usuário participa da chamada
		if (!chamada.participantes.includes(usuarioAtual._id)) {
			throw new Error('Você não participa desta chamada');
		}

		// Se já estiver finalizada, retornar
		if (chamada.status === 'finalizada' || chamada.status === 'cancelada') {
			return null;
		}

		// Calcular duração
		const finalizadaEm = Date.now();
		const iniciadaEm = chamada.iniciadaEm || chamada.criadoEm;
		const duracaoSegundos = Math.floor((finalizadaEm - iniciadaEm) / 1000);

		// Se estiver gravando, parar gravação
		const gravando = chamada.gravando;

		// Atualizar status
		await ctx.db.patch(args.chamadaId, {
			status: 'finalizada',
			finalizadaEm,
			duracaoSegundos,
			gravando: false,
			gravacaoFinalizadaEm: gravando ? finalizadaEm : chamada.gravacaoFinalizadaEm
		});

		return null;
	}
});

/**
 * Cancelar chamada (antes de iniciar)
 */
export const cancelarChamada = mutation({
	args: {
		chamadaId: v.id('chamadas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Apenas anfitrião pode cancelar
		const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
		if (!ehAnfitriao) {
			throw new Error('Apenas o anfitrião pode cancelar a chamada');
		}

		// Se já estiver finalizada, retornar
		if (chamada.status === 'finalizada' || chamada.status === 'cancelada') {
			return null;
		}

		// Atualizar status
		await ctx.db.patch(args.chamadaId, {
			status: 'cancelada',
			finalizadaEm: Date.now()
		});

		return null;
	}
});

/**
 * Adicionar participante à chamada
 */
export const adicionarParticipante = mutation({
	args: {
		chamadaId: v.id('chamadas'),
		usuarioId: v.id('usuarios')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Verificar se usuário já participa
		if (chamada.participantes.includes(args.usuarioId)) {
			return null;
		}

		// Verificar se usuário participa da conversa
		const participa = await verificarParticipanteConversa(ctx, chamada.conversaId, args.usuarioId);
		if (!participa) {
			throw new Error('Usuário não participa desta conversa');
		}

		// Atualizar participantes
		const novosParticipantes = [...chamada.participantes, args.usuarioId];

		// Atualizar configurações
		const configParticipantes = chamada.configuracoes?.participantesConfig || [];
		const novaConfig = [
			...configParticipantes,
			{
				usuarioId: args.usuarioId,
				audioHabilitado: chamada.configuracoes?.audioHabilitado ?? true,
				videoHabilitado: chamada.configuracoes?.videoHabilitado ?? chamada.tipo === 'video',
				forcadoPeloAnfitriao: false
			}
		];

		await ctx.db.patch(args.chamadaId, {
			participantes: novosParticipantes,
			configuracoes: {
				...(chamada.configuracoes || {
					audioHabilitado: true,
					videoHabilitado: chamada.tipo === 'video'
				}),
				participantesConfig: novaConfig
			}
		});

		return null;
	}
});

/**
 * Remover participante da chamada (apenas anfitrião)
 */
export const removerParticipante = mutation({
	args: {
		chamadaId: v.id('chamadas'),
		usuarioId: v.id('usuarios')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Apenas anfitrião pode remover participantes
		const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
		if (!ehAnfitriao) {
			throw new Error('Apenas o anfitrião pode remover participantes');
		}

		// Não pode remover o anfitrião
		if (args.usuarioId === chamada.criadoPor) {
			throw new Error('Não é possível remover o anfitrião');
		}

		// Atualizar participantes
		const novosParticipantes = chamada.participantes.filter((id) => id !== args.usuarioId);

		// Atualizar configurações
		const configParticipantes = chamada.configuracoes?.participantesConfig || [];
		const novaConfig = configParticipantes.filter((config) => config.usuarioId !== args.usuarioId);

		await ctx.db.patch(args.chamadaId, {
			participantes: novosParticipantes,
			configuracoes: {
				...(chamada.configuracoes || {
					audioHabilitado: true,
					videoHabilitado: chamada.tipo === 'video'
				}),
				participantesConfig: novaConfig
			}
		});

		return null;
	}
});

/**
 * Toggle áudio/vídeo de participante (anfitrião controla)
 */
export const toggleAudioVideoParticipante = mutation({
	args: {
		chamadaId: v.id('chamadas'),
		participanteId: v.id('usuarios'),
		tipo: v.union(v.literal('audio'), v.literal('video')),
		habilitado: v.boolean()
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Apenas anfitrião pode controlar outros participantes
		const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
		const ehOProprioParticipante = args.participanteId === usuarioAtual._id;

		if (!ehAnfitriao && !ehOProprioParticipante) {
			throw new Error('Apenas o anfitrião pode controlar outros participantes');
		}

		// Atualizar configurações do participante
		const configParticipantes = chamada.configuracoes?.participantesConfig || [];
		const participanteIndex = configParticipantes.findIndex(
			(config) => config.usuarioId === args.participanteId
		);

		if (participanteIndex === -1) {
			// Adicionar configuração se não existir
			configParticipantes.push({
				usuarioId: args.participanteId,
				audioHabilitado:
					args.tipo === 'audio'
						? args.habilitado
						: (chamada.configuracoes?.audioHabilitado ?? true),
				videoHabilitado:
					args.tipo === 'video'
						? args.habilitado
						: (chamada.configuracoes?.videoHabilitado ?? chamada.tipo === 'video'),
				forcadoPeloAnfitriao: !ehOProprioParticipante && args.habilitado === false
			});
		} else {
			// Atualizar configuração existente
			configParticipantes[participanteIndex] = {
				...configParticipantes[participanteIndex],
				audioHabilitado:
					args.tipo === 'audio'
						? args.habilitado
						: configParticipantes[participanteIndex].audioHabilitado,
				videoHabilitado:
					args.tipo === 'video'
						? args.habilitado
						: configParticipantes[participanteIndex].videoHabilitado,
				forcadoPeloAnfitriao:
					!ehOProprioParticipante && !args.habilitado
						? true
						: configParticipantes[participanteIndex].forcadoPeloAnfitriao
			};
		}

		await ctx.db.patch(args.chamadaId, {
			configuracoes: {
				...(chamada.configuracoes || {
					audioHabilitado: true,
					videoHabilitado: chamada.tipo === 'video'
				}),
				participantesConfig: configParticipantes
			}
		});

		return null;
	}
});

/**
 * Iniciar gravação (apenas anfitrião)
 */
export const iniciarGravacao = mutation({
	args: {
		chamadaId: v.id('chamadas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Apenas anfitrião pode iniciar gravação
		const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
		if (!ehAnfitriao) {
			throw new Error('Apenas o anfitrião pode iniciar a gravação');
		}

		// Se já estiver gravando, retornar
		if (chamada.gravando) {
			return null;
		}

		// Atualizar status de gravação
		await ctx.db.patch(args.chamadaId, {
			gravando: true,
			gravacaoIniciadaPor: usuarioAtual._id,
			gravacaoIniciadaEm: Date.now()
		});

		return null;
	}
});

/**
 * Finalizar gravação (apenas anfitrião)
 */
export const finalizarGravacao = mutation({
	args: {
		chamadaId: v.id('chamadas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) throw new Error('Não autenticado');

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) throw new Error('Chamada não encontrada');

		// Apenas anfitrião pode finalizar gravação
		const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
		if (!ehAnfitriao) {
			throw new Error('Apenas o anfitrião pode finalizar a gravação');
		}

		// Se não estiver gravando, retornar
		if (!chamada.gravando) {
			return null;
		}

		// Atualizar status de gravação
		await ctx.db.patch(args.chamadaId, {
			gravando: false,
			gravacaoFinalizadaEm: Date.now()
		});

		return null;
	}
});

// ========== QUERIES ==========

/**
 * Obter chamada ativa de uma conversa
 */
export const obterChamadaAtiva = query({
	args: {
		conversaId: v.id('conversas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) return null;

		// Verificar se usuário participa da conversa
		const participa = await verificarParticipanteConversa(ctx, args.conversaId, usuarioAtual._id);
		if (!participa) return null;

		// Buscar chamada ativa (aguardando ou em_andamento)
		const todasAtivas = await ctx.db
			.query('chamadas')
			.withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId))
			.filter((q) =>
				q.or(q.eq(q.field('status'), 'aguardando'), q.eq(q.field('status'), 'em_andamento'))
			)
			.collect();

		if (todasAtivas.length === 0) {
			return null;
		}

		// Retornar a chamada mais recente
		return todasAtivas.sort((a, b) => (b.criadoEm || 0) - (a.criadoEm || 0))[0];
	}
});

/**
 * Listar histórico de chamadas de uma conversa
 */
export const listarChamadas = query({
	args: {
		conversaId: v.id('conversas'),
		limit: v.optional(v.number())
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) return [];

		// Verificar se usuário participa da conversa
		const participa = await verificarParticipanteConversa(ctx, args.conversaId, usuarioAtual._id);
		if (!participa) return [];

		// Buscar chamadas
		const chamadas = await ctx.db
			.query('chamadas')
			.withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId))
			.order('desc')
			.take(args.limit || 50);

		return chamadas;
	}
});

/**
 * Verificar se usuário é anfitrião da chamada
 */
export const verificarAnfitriao = query({
	args: {
		chamadaId: v.id('chamadas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) return false;

		return await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
	}
});

/**
 * Obter informações detalhadas da chamada
 */
export const obterChamada = query({
	args: {
		chamadaId: v.id('chamadas')
	},
	handler: async (ctx, args) => {
		const usuarioAtual = await getUsuarioAutenticado(ctx);
		if (!usuarioAtual) return null;

		const chamada = await ctx.db.get(args.chamadaId);
		if (!chamada) return null;

		// Verificar se usuário participa da chamada
		if (!chamada.participantes.includes(usuarioAtual._id)) {
			return null;
		}

		return chamada;
	}
});
