import mongoose from 'mongoose';
import Notification, {
    INotification,
    NotificationAction,
    NotificationCategory
} from '../models/Notification';
import User from '../models/User';

// ─── Notification Service ───

class NotificationService {
    private readonly notificationTexts = {
        en: {
            appointmentBookedTitle: 'Appointment Booked',
            appointmentBookedMessage: (doctorName: string, date: Date, time: string) =>
                `Your appointment with ${doctorName} is scheduled for ${new Date(date).toLocaleDateString('en-US')} at ${time}`,
            appointmentReminderTitle: 'Appointment Reminder',
            appointmentReminderMessage: (doctorName: string, date: Date, time: string) =>
                `Reminder: Your appointment with ${doctorName} is on ${new Date(date).toLocaleDateString('en-US')} at ${time}`,
            prescriptionIssuedTitle: 'New Prescription',
            prescriptionIssuedMessage: (doctorName: string) =>
                `${doctorName} has issued a new prescription for you`,
            paymentReceivedTitle: 'Payment Successful',
            paymentReceivedMessage: (amount: number) =>
                `Payment of ${amount} has been received successfully`,
            subscriptionActivatedTitle: 'Subscription Activated',
            subscriptionActivatedMessage: (planName: string) =>
                `Your ${planName} subscription has been activated`,
        },
        es: {
            appointmentBookedTitle: 'Cita Programada',
            appointmentBookedMessage: (doctorName: string, date: Date, time: string) =>
                `Tu cita con ${doctorName} está programada para el ${new Date(date).toLocaleDateString('es-ES')} a las ${time}`,
            appointmentReminderTitle: 'Recordatorio de Cita',
            appointmentReminderMessage: (doctorName: string, date: Date, time: string) =>
                `Recordatorio: Tu cita con ${doctorName} es el ${new Date(date).toLocaleDateString('es-ES')} a las ${time}`,
            prescriptionIssuedTitle: 'Nueva Prescripción',
            prescriptionIssuedMessage: (doctorName: string) =>
                `${doctorName} te ha emitido una nueva prescripción`,
            paymentReceivedTitle: 'Pago Exitoso',
            paymentReceivedMessage: (amount: number) =>
                `Se ha recibido correctamente un pago de ${amount}`,
            subscriptionActivatedTitle: 'Suscripción Activada',
            subscriptionActivatedMessage: (planName: string) =>
                `Tu suscripción ${planName} ha sido activada`,
        },
    } as const;

    private toObjectId(id: string | mongoose.Types.ObjectId): mongoose.Types.ObjectId {
        if (id instanceof mongoose.Types.ObjectId) {
            return id;
        }
        if (!mongoose.Types.ObjectId.isValid(id)) {
            throw new Error('Invalid ObjectId');
        }
        return new mongoose.Types.ObjectId(id);
    }

    private toOptionalObjectId(
        id?: string | mongoose.Types.ObjectId
    ): mongoose.Types.ObjectId | undefined {
        if (id === undefined) {
            return undefined;
        }
        return this.toObjectId(id);
    }

    private async getUserLanguage(userId: string | mongoose.Types.ObjectId): Promise<'en' | 'es'> {
        const user = await User.findById(this.toObjectId(userId)).select('language');
        return user?.language === 'en' ? 'en' : 'es';
    }

    /**
     * Create a new notification for a user
     */
    async createNotification(
        userId: string | mongoose.Types.ObjectId,
        data: {
            title: string;
            message: string;
            category: NotificationCategory;
            action: NotificationAction;
            referenceId?: string;
            referenceType?: string;
            actionUrl?: string;
            metadata?: Record<string, any>;
            priority?: 'low' | 'medium' | 'high';
        }
    ): Promise<INotification> {
        try {
            const notification = new Notification({
                userId: this.toObjectId(userId),
                title: data.title,
                message: data.message,
                category: data.category,
                action: data.action,
                referenceId: this.toOptionalObjectId(data.referenceId),
                referenceType: data.referenceType,
                actionUrl: data.actionUrl,
                metadata: data.metadata,
                priority: data.priority || 'medium',
                isRead: false
            });

            return await notification.save();
        } catch (error: any) {
            console.error('Error creating notification:', error);
            throw new Error(`Failed to create notification: ${error.message}`);
        }
    }

    /**
     * Create multiple notifications at once (bulk operation)
     */
    async createBulkNotifications(
        userIds: (string | mongoose.Types.ObjectId)[],
        data: {
            title: string;
            message: string;
            category: NotificationCategory;
            action: NotificationAction;
            referenceId?: string;
            referenceType?: string;
            actionUrl?: string;
            metadata?: Record<string, any>;
            priority?: 'low' | 'medium' | 'high';
        }
    ): Promise<INotification[]> {
        try {
            const notifications = userIds.map(userId => ({
                userId: this.toObjectId(userId),
                title: data.title,
                message: data.message,
                category: data.category,
                action: data.action,
                referenceId: this.toOptionalObjectId(data.referenceId),
                referenceType: data.referenceType,
                actionUrl: data.actionUrl,
                metadata: data.metadata,
                priority: data.priority || 'medium',
                isRead: false
            }));

            return await Notification.insertMany(notifications);
        } catch (error: any) {
            console.error('Error creating bulk notifications:', error);
            throw new Error(`Failed to create notifications: ${error.message}`);
        }
    }

    /**
     * Get all notifications for a user (with pagination)
     */
    async getUserNotifications(
        userId: string | mongoose.Types.ObjectId,
        page: number = 1,
        limit: number = 20,
        filter?: {
            category?: NotificationCategory;
            isRead?: boolean;
        }
    ): Promise<{ notifications: INotification[]; total: number; page: number; pages: number }> {
        try {
            const query: any = { userId };
            
            if (filter?.category) {
                query.category = filter.category;
            }
            
            if (filter?.isRead !== undefined) {
                query.isRead = filter.isRead;
            }

            const skip = (page - 1) * limit;
            const total = await Notification.countDocuments(query);
            const notifications = await Notification.find(query)
                .sort({ createdAt: -1 })
                .skip(skip)
                .limit(limit);

            return {
                notifications,
                total,
                page,
                pages: Math.ceil(total / limit)
            };
        } catch (error: any) {
            console.error('Error fetching user notifications:', error);
            throw new Error(`Failed to fetch notifications: ${error.message}`);
        }
    }

    /**
     * Get unread notification count for a user
     */
    async getUnreadCount(userId: string | mongoose.Types.ObjectId): Promise<number> {
        try {
            return await Notification.countDocuments({ userId, isRead: false });
        } catch (error: any) {
            console.error('Error getting unread count:', error);
            throw new Error(`Failed to get unread count: ${error.message}`);
        }
    }

    /**
     * Get a single notification by ID with authorization check
     */
    async getNotificationById(notificationId: string | mongoose.Types.ObjectId, userId: string | mongoose.Types.ObjectId): Promise<INotification | null> {
        try {
            const notification = await Notification.findById(notificationId);
            
            if (!notification) {
                throw new Error('Notification not found');
            }

            if (notification.userId.toString() !== userId.toString()) {
                throw new Error('Unauthorized access to this notification');
            }

            return notification;
        } catch (error: any) {
            console.error('Error fetching notification:', error);
            throw new Error(error.message || `Failed to fetch notification: ${error.message}`);
        }
    }

    /**
     * Mark a notification as read with authorization check
     */
    async markAsRead(notificationId: string | mongoose.Types.ObjectId, userId: string | mongoose.Types.ObjectId): Promise<INotification | null> {
        try {
            const notification = await Notification.findById(notificationId);
            
            if (!notification) {
                throw new Error('Notification not found');
            }

            if (notification.userId.toString() !== userId.toString()) {
                throw new Error('Unauthorized access to this notification');
            }

            return await Notification.findByIdAndUpdate(
                notificationId,
                {
                    isRead: true,
                    readAt: new Date()
                },
                { new: true }
            );
        } catch (error: any) {
            console.error('Error marking notification as read:', error);
            throw new Error(error.message || `Failed to mark notification as read: ${error.message}`);
        }
    }

    /**
     * Mark a notification as unread with authorization check
     */
    async markAsUnread(notificationId: string | mongoose.Types.ObjectId, userId: string | mongoose.Types.ObjectId): Promise<INotification | null> {
        try {
            const notification = await Notification.findById(notificationId);
            
            if (!notification) {
                throw new Error('Notification not found');
            }

            if (notification.userId.toString() !== userId.toString()) {
                throw new Error('Unauthorized access to this notification');
            }

            return await Notification.findByIdAndUpdate(
                notificationId,
                {
                    isRead: false,
                    readAt: null
                },
                { new: true }
            );
        } catch (error: any) {
            console.error('Error marking notification as unread:', error);
            throw new Error(error.message || `Failed to mark notification as unread: ${error.message}`);
        }
    }

    /**
     * Mark all notifications for a user as read
     */
    async markAllAsRead(userId: string | mongoose.Types.ObjectId): Promise<{ modifiedCount: number }> {
        try {
            const result = await Notification.updateMany(
                { userId, isRead: false },
                {
                    isRead: true,
                    readAt: new Date()
                }
            );

            return { modifiedCount: result.modifiedCount };
        } catch (error: any) {
            console.error('Error marking all notifications as read:', error);
            throw new Error(`Failed to mark all as read: ${error.message}`);
        }
    }

    /**
     * Delete a notification with authorization check
     */
    async deleteNotification(notificationId: string | mongoose.Types.ObjectId, userId: string | mongoose.Types.ObjectId): Promise<INotification | null> {
        try {
            const notification = await Notification.findById(notificationId);
            
            if (!notification) {
                throw new Error('Notification not found');
            }

            if (notification.userId.toString() !== userId.toString()) {
                throw new Error('Unauthorized access to this notification');
            }

            return await Notification.findByIdAndDelete(notificationId);
        } catch (error: any) {
            console.error('Error deleting notification:', error);
            throw new Error(error.message || `Failed to delete notification: ${error.message}`);
        }
    }

    /**
     * Delete all notifications for a user
     */
    async deleteAllUserNotifications(userId: string | mongoose.Types.ObjectId): Promise<{ deletedCount: number }> {
        try {
            const result = await Notification.deleteMany({ userId });
            return { deletedCount: result.deletedCount };
        } catch (error: any) {
            console.error('Error deleting all notifications:', error);
            throw new Error(`Failed to delete notifications: ${error.message}`);
        }
    }

    /**
     * Get notification history for a reference with authorization check
     */
    async getNotificationHistory(
        referenceId: string | mongoose.Types.ObjectId,
        referenceType: string,
        userId: string | mongoose.Types.ObjectId
    ): Promise<INotification[]> {
        try {
            return await Notification.find({
                referenceId,
                referenceType,
                userId
            }).sort({ createdAt: -1 });
        } catch (error: any) {
            console.error('Error fetching notification history:', error);
            throw new Error(`Failed to fetch notification history: ${error.message}`);
        }
    }

    /**
     * Clear old notifications (older than specified days)
     */
    async clearOldNotifications(daysOld: number = 30): Promise<{ deletedCount: number }> {
        try {
            const cutoffDate = new Date();
            cutoffDate.setDate(cutoffDate.getDate() - daysOld);

            const result = await Notification.deleteMany({
                createdAt: { $lt: cutoffDate },
                isRead: true
            });

            console.log(`Cleared ${result.deletedCount} old notifications`);
            return { deletedCount: result.deletedCount };
        } catch (error: any) {
            console.error('Error clearing old notifications:', error);
            throw new Error(`Failed to clear old notifications: ${error.message}`);
        }
    }

    /**
     * Get recent notifications for dashboard
     */
    async getRecentNotifications(userId: string | mongoose.Types.ObjectId, limit: number = 10): Promise<INotification[]> {
        try {
            return await Notification.find({ userId })
                .sort({ createdAt: -1 })
                .limit(limit)
                .select('title message category action isRead priority createdAt actionUrl referenceId');
        } catch (error: any) {
            console.error('Error fetching recent notifications:', error);
            throw new Error(`Failed to fetch recent notifications: ${error.message}`);
        }
    }

    /**
     * Create appointment notification for user
     */
    async notifyAppointmentBooked(
        userId: string,
        appointmentId: string,
        appointmentData: {
            doctorName: string;
            appointmentDate: Date;
            appointmentTime: string;
        }
    ): Promise<INotification> {
        const lang = await this.getUserLanguage(userId);
        const text = this.notificationTexts[lang];
        return this.createNotification(userId, {
            title: text.appointmentBookedTitle,
            message: text.appointmentBookedMessage(appointmentData.doctorName, appointmentData.appointmentDate, appointmentData.appointmentTime),
            category: NotificationCategory.APPOINTMENT,
            action: NotificationAction.APPOINTMENT_BOOKED,
            referenceId: appointmentId,
            referenceType: 'Appointment',
            actionUrl: `/appointments`,
            metadata: appointmentData,
            priority: 'high'
        });
    }

    /**
     * Create appointment reminder notification
     */
    async notifyAppointmentReminder(
        userId: string,
        appointmentId: string,
        appointmentData: {
            doctorName: string;
            appointmentDate: Date;
            appointmentTime: string;
        }
    ): Promise<INotification> {
        const lang = await this.getUserLanguage(userId);
        const text = this.notificationTexts[lang];
        return this.createNotification(userId, {
            title: text.appointmentReminderTitle,
            message: text.appointmentReminderMessage(appointmentData.doctorName, appointmentData.appointmentDate, appointmentData.appointmentTime),
            category: NotificationCategory.APPOINTMENT,
            action: NotificationAction.APPOINTMENT_REMINDER,
            referenceId: appointmentId,
            referenceType: 'Appointment',
            actionUrl: `/appointments`,
            metadata: appointmentData,
            priority: 'medium'
        });
    }

    /**
     * Create prescription notification
     */
    async notifyPrescriptionIssued(
        userId: string,
        prescriptionId: string,
        prescriptionData: {
            doctorName: string;
            medicines: string[];
        }
    ): Promise<INotification> {
        const lang = await this.getUserLanguage(userId);
        const text = this.notificationTexts[lang];
        return this.createNotification(userId, {
            title: text.prescriptionIssuedTitle,
            message: text.prescriptionIssuedMessage(prescriptionData.doctorName),
            category: NotificationCategory.PRESCRIPTION,
            action: NotificationAction.PRESCRIPTION_ISSUED,
            referenceId: prescriptionId,
            referenceType: 'Prescription',
            actionUrl: `/prescriptions`,
            metadata: prescriptionData,
            priority: 'high'
        });
    }

    /**
     * Create payment notification
     */
    async notifyPaymentReceived(
        userId: string,
        paymentId: string,
        amount: number
    ): Promise<INotification> {
        const lang = await this.getUserLanguage(userId);
        const text = this.notificationTexts[lang];
        return this.createNotification(userId, {
            title: text.paymentReceivedTitle,
            message: text.paymentReceivedMessage(amount),
            category: NotificationCategory.PAYMENT,
            action: NotificationAction.PAYMENT_RECEIVED,
            referenceId: paymentId,
            referenceType: 'Payment',
            actionUrl: `/payments`,
            metadata: { amount },
            priority: 'high'
        });
    }

    /**
     * Create subscription notification
     */
    async notifySubscriptionActivated(
        userId: string,
        subscriptionId: string,
        planName: string
    ): Promise<INotification> {
        const lang = await this.getUserLanguage(userId);
        const text = this.notificationTexts[lang];
        return this.createNotification(userId, {
            title: text.subscriptionActivatedTitle,
            message: text.subscriptionActivatedMessage(planName),
            category: NotificationCategory.SUBSCRIPTION,
            action: NotificationAction.SUBSCRIPTION_ACTIVATED,
            referenceId: subscriptionId,
            referenceType: 'Subscription',
            actionUrl: `/subscription`,
            metadata: { planName },
            priority: 'high'
        });
    }
}

export default new NotificationService();
