import Notification, { INotification } from '../models/Notification.js';
import StockItem from '../models/StockItem.js';
import Instrument from '../models/Instrument.js';
import { addMonths, isBefore } from 'date-fns';

export const notificationService = {
    // Criar uma notificação
    async create(data: Partial<INotification>) {
        try {
            const notification = new Notification(data);
            await notification.save();
            return notification;
        } catch (error) {
            console.error('Error creating notification:', error);
            throw error;
        }
    },

    // Verificar se já existe uma notificação recente para evitar spam
    async isAlreadyNotified(orgId: string, metadata: Record<string, string>, graceDays: number = 30) {
        try {
            const graceDate = new Date();
            graceDate.setDate(graceDate.getDate() - graceDays);

            const query: Record<string, unknown> = {
                organizationId: orgId
            };

            // Adicionar campos de metadata à query
            for (const [key, value] of Object.entries(metadata)) {
                query[`metadata.${key}`] = value;
            }

            // Verificar se existe alguma notificação com essa metadata nos últimos graceDays
            // Independente de estar lida ou não, para evitar duplicidade.
            query.createdAt = { $gte: graceDate };

            const existing = await Notification.findOne(query);
            return !!existing;
        } catch (error) {
            console.error('Error checking notification existence:', error);
            return false;
        }
    },

    // Obter notificações de um usuário (ou globais da organização)
    async getUserNotifications(userId: string, organizationId: string, includeArchived: boolean = false) {
        try {
            const query: Record<string, unknown> = {
                organizationId,
                $or: [
                    { recipientId: userId },
                    { recipientId: null } // Notificações globais
                ],
                deletedBy: { $ne: userId } // Não mostrar as deletadas pelo usuário
            };

            if (!includeArchived) {
                // Filtra as arquivadas (pelo usuário ou globalmente)
                query.isArchived = false;
                query.archivedBy = { $ne: userId };
            }

            return await Notification.find(query).sort({ createdAt: -1 }).limit(50);
        } catch (error) {
            console.error('Error fetching notifications:', error);
            throw error;
        }
    },

    // Marcar como lida
    async markAsRead(id: string) {
        try {
            return await Notification.findByIdAndUpdate(id, { isRead: true }, { new: true });
        } catch (error) {
            console.error('Error marking notification as read:', error);
            throw error;
        }
    },

    // Marcar todas como lidas para um usuário
    async markAllAsRead(userId: string, organizationId: string) {
        try {
            return await Notification.updateMany(
                {
                    organizationId,
                    $or: [
                        { recipientId: userId },
                        { recipientId: null }
                    ],
                    isRead: false
                },
                { isRead: true }
            );
        } catch (error) {
            console.error('Error marking all notifications as read:', error);
            throw error;
        }
    },

    // Arquivar uma notificação para um usuário
    async archive(id: string, userId: string) {
        try {
            const notification = await Notification.findById(id);
            if (!notification) return null;

            if (notification.recipientId) {
                // Notificação pessoal
                notification.isArchived = true;
                notification.isRead = true;
            } else {
                // Notificação global
                if (!notification.archivedBy.includes(userId)) {
                    notification.archivedBy.push(userId);
                }
                // Marcar como lida também? Opcional
                if (!notification.readBy?.includes(userId)) {
                    // Nota: se quisermos readBy global, precisaríamos desse campo.
                    // Para simplificar, vamos assumir que arquivar esconde da lista ativa.
                }
            }
            return await notification.save();
        } catch (error) {
            console.error('Error archiving notification:', error);
            throw error;
        }
    },

    // Deletar (esconder) uma notificação para um usuário
    async softDelete(id: string, userId: string) {
        try {
            const notification = await Notification.findById(id);
            if (!notification) return null;

            if (notification.recipientId && notification.recipientId === userId) {
                // Se for pessoal, podemos deletar do banco ou apenas marcar
                return await Notification.findByIdAndDelete(id);
            } else {
                // Se for global, apenas adicionar ao deletedBy
                if (!notification.deletedBy.includes(userId)) {
                    notification.deletedBy.push(userId);
                }
                return await notification.save();
            }
        } catch (error) {
            console.error('Error soft deleting notification:', error);
            throw error;
        }
    },

    // Limpar todas (esconder todas as atuais)
    async clearAll(userId: string, organizationId: string) {
        try {
            // Para notificações pessoais: Deletar
            await Notification.deleteMany({
                organizationId,
                recipientId: userId
            });

            // Para notificações globais: Marcar como deletadas por esse usuário
            const globalNotifications = await Notification.find({
                organizationId,
                recipientId: null,
                deletedBy: { $ne: userId }
            });

            for (const notif of globalNotifications) {
                notif.deletedBy.push(userId);
                await notif.save();
            }

            return { success: true };
        } catch (error) {
            console.error('Error clearing all notifications:', error);
            throw error;
        }
    },

    // Verificar vencimentos de estoque e gerar notificações
    async checkStockExpirations() {
        console.log('Running stock expiration checkJob...');
        try {
            // Buscar todos os itens de estoque com data de validade que ainda não venceram ou venceram recentemente
            // Otimização: Em um sistema real, faríamos isso por query direta, mas aqui vamos iterar para aplicar a lógica de 2 meses, 1 mês, vencido.
            const stockItems = await StockItem.find({ expirationDate: { $exists: true, $ne: null }, quantity: { $gt: 0 } });

            const now = new Date();
            const twoMonthsFromNow = addMonths(now, 2);
            const oneMonthFromNow = addMonths(now, 1);

            for (const item of stockItems) {
                if (!item.expirationDate) continue;

                const expirationDate = new Date(item.expirationDate);
                const itemId = item._id.toString();
                const orgId = item.organizationId;

                if (!orgId) continue;

                let message = '';
                let title = '';
                let type: 'warning' | 'error' = 'warning';

                // Lógica de notificação
                // 1. Vencido
                if (isBefore(expirationDate, now)) {
                    title = 'Item Vencido';
                    message = `O item ${item.rrNumber} - Lote ${item.batchNumber} venceu em ${expirationDate.toLocaleDateString()}.`;
                    type = 'error';

                    const notified = await this.isAlreadyNotified(orgId.toString(), {
                        stockItemId: itemId,
                        triggerType: 'expired'
                    });

                    if (!notified) {
                        await this.create({
                            organizationId: orgId,
                            title,
                            message,
                            type,
                            metadata: { stockItemId: itemId, triggerType: 'expired' }
                        });
                    }
                }
                // 2. Vence em 1 mês (aprox)
                else if (isBefore(expirationDate, oneMonthFromNow)) {
                    title = 'Vencimento Próximo (1 mês)';
                    message = `O item ${item.rrNumber} - Lote ${item.batchNumber} vencerá em menos de 1 mês (${expirationDate.toLocaleDateString()}).`;

                    const notified = await this.isAlreadyNotified(orgId.toString(), {
                        stockItemId: itemId,
                        triggerType: 'expire_1_month'
                    });

                    if (!notified) {
                        await this.create({
                            organizationId: orgId,
                            title,
                            message,
                            type: 'warning',
                            metadata: { stockItemId: itemId, triggerType: 'expire_1_month' }
                        });
                    }

                }
                // 3. Vence em 2 meses (aprox)
                else if (isBefore(expirationDate, twoMonthsFromNow)) {
                    title = 'Vencimento em 2 meses';
                    message = `O item ${item.rrNumber} - Lote ${item.batchNumber} vencerá em 2 meses (${expirationDate.toLocaleDateString()}).`;

                    const notified = await this.isAlreadyNotified(orgId.toString(), {
                        stockItemId: itemId,
                        triggerType: 'expire_2_months'
                    });

                    if (!notified) {
                        await this.create({
                            organizationId: orgId,
                            title,
                            message,
                            type: 'info',
                            metadata: { stockItemId: itemId, triggerType: 'expire_2_months' }
                        });
                    }
                }
            }
        } catch (error) {
            console.error('Error in checkStockExpirations:', error);
        }
    },

    // Verificar calibração de instrumentos
    async checkInstrumentCalibrations() {
        console.log('Running instrument calibration checkJob...');
        try {
            const instruments = await Instrument.find({
                calibrationExpirationDate: { $exists: true, $ne: null },
                status: { $ne: 'inactive' }
            });

            const now = new Date();
            const twoMonthsFromNow = addMonths(now, 2);
            const oneMonthFromNow = addMonths(now, 1);

            for (const instrument of instruments) {
                if (!instrument.calibrationExpirationDate) continue;

                const expirationDate = new Date(instrument.calibrationExpirationDate);
                const instrumentId = instrument._id.toString();
                const orgId = instrument.organizationId;

                if (!orgId) continue;

                let title = '';
                let message = '';
                let type: 'info' | 'warning' | 'error' = 'info';
                let triggerType = '';

                // 1. Vencido
                if (isBefore(expirationDate, now)) {
                    title = 'Calibração Vencida';
                    message = `O instrumento ${instrument.name} (${instrument.serialNumber}) está com a calibração vencida desde ${expirationDate.toLocaleDateString()}.`;
                    type = 'error';
                    triggerType = 'calibration_expired';

                    // Atualizar status para expired se não estiver
                    if (instrument.status !== 'expired') {
                        instrument.status = 'expired';
                        await instrument.save();
                    }

                }
                // 2. Vence em 1 mês
                else if (isBefore(expirationDate, oneMonthFromNow)) {
                    title = 'Calibração vence em 1 mês';
                    message = `A calibração do instrumento ${instrument.name} (${instrument.serialNumber}) vence em ${expirationDate.toLocaleDateString()}.`;
                    type = 'warning';
                    triggerType = 'calibration_1_month';
                }
                // 3. Vence em 2 meses
                else if (isBefore(expirationDate, twoMonthsFromNow)) {
                    title = 'Calibração vence em 2 meses';
                    message = `A calibração do instrumento ${instrument.name} (${instrument.serialNumber}) vence em ${expirationDate.toLocaleDateString()}.`;
                    type = 'info';
                    triggerType = 'calibration_2_months';
                } else {
                    continue; // Não precisa notificar
                }

                // Evitar spam
                const notified = await this.isAlreadyNotified(orgId.toString(), {
                    instrumentId,
                    triggerType
                });

                if (!notified) {
                    await this.create({
                        organizationId: orgId,
                        title,
                        message,
                        type,
                        metadata: { instrumentId, triggerType }
                    });
                }
            }
        } catch (error) {
            console.error('Error in checkInstrumentCalibrations:', error);
        }
    },

    // Verificar se o estoque está abaixo do mínimo (Aggregated by Product + Color)
    async checkLowStock(stockItemId: string) {
        try {
            const item = await StockItem.findById(stockItemId).populate('dataSheetId', 'name manufacturer');
            if (!item || !item.minStock || item.minStock <= 0) return;

            const orgId = item.organizationId;
            if (!orgId) return;

            // Aggregate total quantity for this Product + Color
            const siblings = await StockItem.find({
                organizationId: orgId,
                dataSheetId: item.dataSheetId,
                color: item.color
            });

            const totalQuantity = siblings.reduce((sum, s) => sum + s.quantity, 0);

            if (totalQuantity < item.minStock) {
                // Check throttling
                const notified = await this.isAlreadyNotified(orgId.toString(), {
                    stockItemId: stockItemId, // Keep using specific item ID as reference or maybe composite key?
                    // Let's use a composite key for the trigger to avoid spamming for every batch in the group
                    productColorKey: `${item.dataSheetId._id || item.dataSheetId}-${item.color}`,
                    triggerType: 'low_stock_aggregated'
                }, 3);

                if (!notified) {
                    await this.create({
                        organizationId: orgId,
                        title: 'Estoque Baixo (Total)',
                        message: `O produto ${item.dataSheetId?.name} (Cor: ${item.color || 'N/A'}) atingiu o nível crítico. Total: ${totalQuantity.toFixed(1)}${item.unit}. (Mínimo: ${item.minStock}${item.unit})`,
                        type: 'error',
                        metadata: {
                            stockItemId,
                            productColorKey: `${item.dataSheetId._id || item.dataSheetId}-${item.color}`,
                            triggerType: 'low_stock_aggregated'
                        }
                    });
                }
            }
        } catch (error) {
            console.error('Error checking low stock:', error);
        }
    }
};
