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

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

async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
	const user = await getCurrentUserFunction(ctx);
	if (!user) throw new Error('Unauthorized');
	return user;
}

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

export const list = query({
	args: {},
	returns: v.array(
		v.object({
			_id: v.id('pedidos'),
			_creationTime: v.number(),
			numeroSei: v.optional(v.string()),
			status: v.union(
				v.literal('em_rascunho'),
				v.literal('aguardando_aceite'),
				v.literal('em_analise'),
				v.literal('precisa_ajustes'),
				v.literal('cancelado'),
				v.literal('concluido')
			),
			acaoId: v.optional(v.id('acoes')),
			criadoPor: v.id('usuarios'),
			criadoEm: v.number(),
			atualizadoEm: v.number()
		})
	),
	handler: async (ctx) => {
		return await ctx.db.query('pedidos').collect();
	}
});

export const get = query({
	args: { id: v.id('pedidos') },
	returns: v.union(
		v.object({
			_id: v.id('pedidos'),
			_creationTime: v.number(),
			numeroSei: v.optional(v.string()),
			status: v.union(
				v.literal('em_rascunho'),
				v.literal('aguardando_aceite'),
				v.literal('em_analise'),
				v.literal('precisa_ajustes'),
				v.literal('cancelado'),
				v.literal('concluido')
			),
			acaoId: v.optional(v.id('acoes')),
			criadoPor: v.id('usuarios'),
			criadoEm: v.number(),
			atualizadoEm: v.number()
		}),
		v.null()
	),
	handler: async (ctx, args) => {
		return await ctx.db.get(args.id);
	}
});

export const getItems = query({
	args: { pedidoId: v.id('pedidos') },
	returns: v.array(
		v.object({
			_id: v.id('pedidoItems'),
			_creationTime: v.number(),
			pedidoId: v.id('pedidos'),
			produtoId: v.id('produtos'),
			valorEstimado: v.string(),
			valorReal: v.optional(v.string()),
			quantidade: v.number(),
			adicionadoPor: v.id('funcionarios'),
			adicionadoPorNome: v.string(),
			criadoEm: v.number()
		})
	),
	handler: async (ctx, args) => {
		const items = await ctx.db
			.query('pedidoItems')
			.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
			.collect();

		// Get employee names
		const itemsWithNames = await Promise.all(
			items.map(async (item) => {
				const funcionario = await ctx.db.get(item.adicionadoPor);
				return {
					...item,
					adicionadoPorNome: funcionario?.nome || 'Desconhecido'
				};
			})
		);

		return itemsWithNames;
	}
});

export const getHistory = query({
	args: { pedidoId: v.id('pedidos') },
	handler: async (ctx, args) => {
		const history = await ctx.db
			.query('historicoPedidos')
			.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
			.order('desc')
			.collect();

		// Get user names
		const historyWithNames = await Promise.all(
			history.map(async (entry) => {
				const usuario = await ctx.db.get(entry.usuarioId);
				return {
					_id: entry._id,
					_creationTime: entry._creationTime,
					pedidoId: entry.pedidoId,
					usuarioId: entry.usuarioId,
					usuarioNome: usuario?.nome || 'Desconhecido',
					acao: entry.acao,
					detalhes: entry.detalhes,
					data: entry.data
				};
			})
		);

		return historyWithNames;
	}
});

export const checkExisting = query({
	args: {
		acaoId: v.optional(v.id('acoes')),
		numeroSei: v.optional(v.string()),
		produtoIds: v.optional(v.array(v.id('produtos')))
	},
	returns: v.array(
		v.object({
			_id: v.id('pedidos'),
			_creationTime: v.number(),
			numeroSei: v.optional(v.string()),
			status: v.union(
				v.literal('em_rascunho'),
				v.literal('aguardando_aceite'),
				v.literal('em_analise'),
				v.literal('precisa_ajustes'),
				v.literal('cancelado'),
				v.literal('concluido')
			),
			acaoId: v.optional(v.id('acoes')),
			criadoPor: v.id('usuarios'),
			criadoEm: v.number(),
			atualizadoEm: v.number(),
			matchingItems: v.optional(
				v.array(
					v.object({
						produtoId: v.id('produtos'),
						quantidade: v.number()
					})
				)
			)
		})
	),
	handler: async (ctx, args) => {
		const user = await getCurrentUserFunction(ctx);
		if (!user) return [];

		const openStatuses: Array<
			'em_rascunho' | 'aguardando_aceite' | 'em_analise' | 'precisa_ajustes'
		> = ['em_rascunho', 'aguardando_aceite', 'em_analise', 'precisa_ajustes'];

		// 1) Buscar todos os pedidos "abertos" usando o índice by_status
		let pedidosAbertos: Doc<'pedidos'>[] = [];
		for (const status of openStatuses) {
			const partial = await ctx.db
				.query('pedidos')
				.withIndex('by_status', (q) => q.eq('status', status))
				.collect();
			pedidosAbertos = pedidosAbertos.concat(partial);
		}

		// 2) Filtros opcionais: acaoId e numeroSei
		pedidosAbertos = pedidosAbertos.filter((p) => {
			if (args.acaoId && p.acaoId !== args.acaoId) return false;
			if (args.numeroSei && p.numeroSei !== args.numeroSei) return false;
			return true;
		});

		// 3) Filtro por produtos (se informado) e coleta de matchingItems
		const resultados = [];

		for (const pedido of pedidosAbertos) {
			let include = true;
			let matchingItems: { produtoId: Id<'produtos'>; quantidade: number }[] = [];

			// Se houver filtro de produtos, verificamos se o pedido tem ALGUM dos produtos
			if (args.produtoIds && args.produtoIds.length > 0) {
				const items = await ctx.db
					.query('pedidoItems')
					.withIndex('by_pedidoId', (q) => q.eq('pedidoId', pedido._id))
					.collect();

				// const pedidoProdutoIds = new Set(items.map((i) => i.produtoId)); // Unused
				const matching = items.filter((i) => args.produtoIds?.includes(i.produtoId));

				if (matching.length > 0) {
					matchingItems = matching.map((i) => ({
						produtoId: i.produtoId,
						quantidade: i.quantidade
					}));
				} else {
					// Se foi pedido filtro por produtos e não tem nenhum match, ignoramos este pedido
					// A MENOS que tenha dado match por numeroSei ou acaoId?
					// A regra original era: "Filtro por produtos (se informado)"
					// Se o usuário informou produtos, ele quer ver pedidos que tenham esses produtos.
					// Mas se ele TAMBÉM informou numeroSei, talvez ele queira ver aquele pedido específico mesmo sem o produto?
					// Vamos manter a lógica de "E": se informou produtos, tem que ter o produto.
					include = false;
				}
			}

			if (include) {
				resultados.push({
					_id: pedido._id,
					_creationTime: pedido._creationTime,
					numeroSei: pedido.numeroSei,
					status: pedido.status,
					acaoId: pedido.acaoId,
					criadoPor: pedido.criadoPor,
					criadoEm: pedido.criadoEm,
					atualizadoEm: pedido.atualizadoEm,
					matchingItems: matchingItems.length > 0 ? matchingItems : undefined
				});
			}
		}

		return resultados;
	}
});

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

export const create = mutation({
	args: {
		numeroSei: v.optional(v.string()),
		acaoId: v.optional(v.id('acoes'))
	},
	returns: v.id('pedidos'),
	handler: async (ctx, args) => {
		const user = await getUsuarioAutenticado(ctx);

		// 1. Check Config
		const config = await ctx.db.query('config').first();
		if (!config || !config.comprasSetorId) {
			throw new Error('Setor de Compras não configurado. Contate o administrador.');
		}

		// 2. Check Existing (Double check)
		if (args.acaoId) {
			const existing = await ctx.db
				.query('pedidos')
				.withIndex('by_acaoId', (q) => q.eq('acaoId', args.acaoId))
				.filter((q) =>
					q.or(
						q.eq(q.field('status'), 'em_rascunho'),
						q.eq(q.field('status'), 'aguardando_aceite'),
						q.eq(q.field('status'), 'em_analise'),
						q.eq(q.field('status'), 'precisa_ajustes')
					)
				)
				.first();

			if (existing) {
				throw new Error('Já existe um pedido em andamento para esta ação.');
			}
		}

		// 3. Create Order
		const pedidoId = await ctx.db.insert('pedidos', {
			numeroSei: args.numeroSei,
			status: 'em_rascunho',
			acaoId: args.acaoId,
			criadoPor: user._id,
			criadoEm: Date.now(),
			atualizadoEm: Date.now()
		});

		// 4. Create History
		await ctx.db.insert('historicoPedidos', {
			pedidoId,
			usuarioId: user._id,
			acao: 'criacao',
			detalhes: JSON.stringify({ numeroSei: args.numeroSei, acaoId: args.acaoId }),
			data: Date.now()
		});

		return pedidoId;
	}
});

export const updateSeiNumber = mutation({
	args: {
		pedidoId: v.id('pedidos'),
		numeroSei: v.string()
	},
	returns: v.null(),
	handler: async (ctx, args) => {
		const user = await getUsuarioAutenticado(ctx);
		const pedido = await ctx.db.get(args.pedidoId);
		if (!pedido) throw new Error('Pedido not found');

		// Check if SEI number is already taken by another order
		const existing = await ctx.db
			.query('pedidos')
			.filter((q) =>
				q.and(q.eq(q.field('numeroSei'), args.numeroSei), q.neq(q.field('_id'), args.pedidoId))
			)
			.first();

		if (existing) {
			throw new Error('Este número SEI já está em uso por outro pedido.');
		}

		const oldSei = pedido.numeroSei;

		await ctx.db.patch(args.pedidoId, {
			numeroSei: args.numeroSei,
			atualizadoEm: Date.now()
		});

		await ctx.db.insert('historicoPedidos', {
			pedidoId: args.pedidoId,
			usuarioId: user._id,
			acao: 'atualizacao_sei',
			detalhes: JSON.stringify({ de: oldSei, para: args.numeroSei }),
			data: Date.now()
		});
	}
});

export const addItem = mutation({
	args: {
		pedidoId: v.id('pedidos'),
		produtoId: v.id('produtos'),
		valorEstimado: v.string(),
		quantidade: v.number()
	},
	returns: v.null(),
	handler: async (ctx, args) => {
		const user = await getUsuarioAutenticado(ctx);

		// Ensure user has a funcionarioId linked
		if (!user.funcionarioId) {
			throw new Error('Usuário não vinculado a um funcionário.');
		}

		await ctx.db.insert('pedidoItems', {
			pedidoId: args.pedidoId,
			produtoId: args.produtoId,
			valorEstimado: args.valorEstimado,
			quantidade: args.quantidade,
			adicionadoPor: user.funcionarioId,
			criadoEm: Date.now()
		});

		await ctx.db.patch(args.pedidoId, { atualizadoEm: Date.now() });

		await ctx.db.insert('historicoPedidos', {
			pedidoId: args.pedidoId,
			usuarioId: user._id,
			acao: 'adicao_item',
			detalhes: JSON.stringify({
				produtoId: args.produtoId,
				valor: args.valorEstimado,
				quantidade: args.quantidade
			}),
			data: Date.now()
		});
	}
});

export const updateItemQuantity = mutation({
	args: {
		itemId: v.id('pedidoItems'),
		novaQuantidade: v.number()
	},
	returns: v.null(),
	handler: async (ctx, args) => {
		const user = await getUsuarioAutenticado(ctx);

		if (!user.funcionarioId) {
			throw new Error('Usuário não vinculado a um funcionário.');
		}

		const item = await ctx.db.get(args.itemId);
		if (!item) throw new Error('Item não encontrado.');

		const quantidadeAnterior = item.quantidade;

		// Check permission: only item owner can decrease quantity
		const isOwner = item.adicionadoPor === user.funcionarioId;
		const isDecreasing = args.novaQuantidade < quantidadeAnterior;

		if (isDecreasing && !isOwner) {
			throw new Error(
				'Apenas quem adicionou este item pode diminuir a quantidade. Você pode apenas aumentar.'
			);
		}

		// Update quantity
		await ctx.db.patch(args.itemId, { quantidade: args.novaQuantidade });
		await ctx.db.patch(item.pedidoId, { atualizadoEm: Date.now() });

		// Create history entry
		await ctx.db.insert('historicoPedidos', {
			pedidoId: item.pedidoId,
			usuarioId: user._id,
			acao: 'alteracao_quantidade',
			detalhes: JSON.stringify({
				produtoId: item.produtoId,
				quantidadeAnterior,
				novaQuantidade: args.novaQuantidade
			}),
			data: Date.now()
		});
	}
});

export const removeItem = mutation({
	args: {
		itemId: v.id('pedidoItems')
	},
	returns: v.null(),
	handler: async (ctx, args) => {
		const user = await getUsuarioAutenticado(ctx);

		const item = await ctx.db.get(args.itemId);
		if (!item) throw new Error('Item not found');

		await ctx.db.delete(args.itemId);
		await ctx.db.patch(item.pedidoId, { atualizadoEm: Date.now() });

		await ctx.db.insert('historicoPedidos', {
			pedidoId: item.pedidoId,
			usuarioId: user._id,
			acao: 'remocao_item',
			detalhes: JSON.stringify({ produtoId: item.produtoId, valor: item.valorEstimado }),
			data: Date.now()
		});
	}
});

export const updateStatus = mutation({
	args: {
		pedidoId: v.id('pedidos'),
		novoStatus: v.union(
			v.literal('em_rascunho'),
			v.literal('aguardando_aceite'),
			v.literal('em_analise'),
			v.literal('precisa_ajustes'),
			v.literal('cancelado'),
			v.literal('concluido')
		)
	},
	returns: v.null(),
	handler: async (ctx, args) => {
		const user = await getUsuarioAutenticado(ctx);
		const pedido = await ctx.db.get(args.pedidoId);
		if (!pedido) throw new Error('Pedido not found');

		const oldStatus = pedido.status;

		await ctx.db.patch(args.pedidoId, {
			status: args.novoStatus,
			atualizadoEm: Date.now()
		});

		await ctx.db.insert('historicoPedidos', {
			pedidoId: args.pedidoId,
			usuarioId: user._id,
			acao: 'alteracao_status',
			detalhes: JSON.stringify({ de: oldStatus, para: args.novoStatus }),
			data: Date.now()
		});

		// Trigger Notifications
		await ctx.scheduler.runAfter(0, internal.pedidos.notifyStatusChange, {
			pedidoId: args.pedidoId,
			oldStatus,
			newStatus: args.novoStatus,
			actorId: user._id
		});
	}
});

// ========== INTERNAL (NOTIFICATIONS) ==========

export const notifyStatusChange = internalMutation({
	args: {
		pedidoId: v.id('pedidos'),
		oldStatus: v.string(),
		newStatus: v.string(),
		actorId: v.id('usuarios')
	},
	returns: v.null(),
	handler: async (ctx, args) => {
		const pedido = await ctx.db.get(args.pedidoId);
		if (!pedido) return;

		const actor = await ctx.db.get(args.actorId);
		const actorName = actor ? actor.nome : 'Alguém';

		const recipients = new Set<string>(); // Set of User IDs

		// 1. If status is "aguardando_aceite", notify Purchasing Sector
		if (args.newStatus === 'aguardando_aceite') {
			const config = await ctx.db.query('config').first();
			if (config && config.comprasSetorId) {
				// Find all employees in this sector
				const funcionarioSetores = await ctx.db
					.query('funcionarioSetores')
					.withIndex('by_setorId', (q) => q.eq('setorId', config.comprasSetorId!))
					.collect();

				const funcionarioIds = funcionarioSetores.map((fs) => fs.funcionarioId);

				// Find users linked to these employees
				for (const fId of funcionarioIds) {
					const user = await ctx.db
						.query('usuarios')
						.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', fId))
						.first();
					if (user) recipients.add(user._id);
				}
			}
		}

		// 2. Notify "Involved" users (Creator + Item Adders)
		// Always notify creator (unless they are the actor)
		if (pedido.criadoPor !== args.actorId) {
			recipients.add(pedido.criadoPor);
		}

		// Notify item adders
		const items = await ctx.db
			.query('pedidoItems')
			.withIndex('by_pedidoId', (q) => q.eq('pedidoId', args.pedidoId))
			.collect();

		for (const item of items) {
			const user = await ctx.db
				.query('usuarios')
				.withIndex('by_funcionarioId', (q) => q.eq('funcionarioId', item.adicionadoPor))
				.first();
			if (user && user._id !== args.actorId) {
				recipients.add(user._id);
			}
		}

		// Send Notifications
		for (const recipientId of recipients) {
			const recipientIdTyped = recipientId as Id<'usuarios'>;

			// 1. In-App Notification
			await ctx.db.insert('notificacoes', {
				usuarioId: recipientIdTyped,
				tipo: 'alerta_seguranca', // Using alerta_seguranca as the closest match for system notifications
				titulo: `Pedido ${pedido.numeroSei || 'sem número SEI'} atualizado`,
				descricao: `Status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}.`,
				lida: false,
				criadaEm: Date.now(),
				remetenteId: args.actorId
			});

			// 2. Email Notification (Async)
			const recipientUser = await ctx.db.get(recipientIdTyped);
			if (recipientUser && recipientUser.email) {
				// Using enfileirarEmail directly
				await ctx.scheduler.runAfter(0, api.email.enfileirarEmail, {
					destinatario: recipientUser.email,
					destinatarioId: recipientIdTyped,
					assunto: `Atualização no Pedido ${pedido.numeroSei || 'sem número SEI'}`,
					corpo: `O pedido ${pedido.numeroSei || 'sem número SEI'} teve seu status alterado de "${args.oldStatus}" para "${args.newStatus}" por ${actorName}.`,
					enviadoPor: args.actorId
				});
			}
		}
	}
});
