import { Queue, Job } from 'bullmq';
import { createQueue } from '../config/redis';
import { IAppointment } from '../models/Appointment';
import { NotificationPurpose } from '../models/NotificationLog';

// ─── Job Data Interface ───

export interface NotificationJobData {
    appointmentId: string;
    patientId: string;
    doctorId: string;
    purpose: NotificationPurpose;
    scheduledTime: string;          // ISO string of appointmentDate when job was created
    notificationVersion: number;    // Version at time of scheduling (for stale job detection)
}

// ─── Reminder Strategy ───

const REMINDER_STRATEGY = [
    { purpose: NotificationPurpose.APPOINTMENT_REMINDER_24H, offsetMs: 24 * 60 * 60 * 1000 },
    { purpose: NotificationPurpose.APPOINTMENT_REMINDER_4H, offsetMs: 4 * 60 * 60 * 1000 },
    { purpose: NotificationPurpose.APPOINTMENT_REMINDER_2H, offsetMs: 2 * 60 * 60 * 1000 },
    { purpose: NotificationPurpose.APPOINTMENT_REMINDER_AT_TIME, offsetMs: 0 },
];

// ─── Queue Service (Producer) ───

class NotificationQueueService {
    private queue: Queue;
    private readonly QUEUE_NAME = 'appointment-notifications';

    constructor() {
        this.queue = createQueue(this.QUEUE_NAME);
    }

    /**
     * Schedule all reminders for an appointment.
     * Returns an array of BullMQ job IDs that were created.
     * These IDs should be stored on the Appointment document for active cancellation.
     */
    async scheduleAppointmentReminders(appointment: IAppointment): Promise<string[]> {
        if (!appointment.appointmentDate) return [];

        const appointmentTime = new Date(appointment.appointmentDate).getTime();
        const now = Date.now();
        const scheduledJobIds: string[] = [];

        for (const reminder of REMINDER_STRATEGY) {
            const triggerTime = appointmentTime - reminder.offsetMs;

            // Only schedule if trigger time is in the future
            if (triggerTime > now) {
                const delay = triggerTime - now;

                const job = await this.addJob({
                    appointmentId: String(appointment._id),
                    patientId: String(appointment.patientId),
                    doctorId: String(appointment.doctorId),
                    purpose: reminder.purpose,
                    scheduledTime: appointment.appointmentDate.toISOString(),
                    notificationVersion: appointment.notificationVersion || 1
                }, delay);

                if (job?.id) {
                    scheduledJobIds.push(job.id);
                }
            }
        }

        console.log(`[Queue] Scheduled ${scheduledJobIds.length} reminders for appointment ${appointment._id}`);
        return scheduledJobIds;
    }

    /**
     * Remove previously scheduled jobs by their IDs.
     * This is called on cancellation or reschedule to actively clean up delayed jobs.
     */
    async removeScheduledJobs(jobIds: string[]): Promise<{ removed: number; failed: number }> {
        let removed = 0;
        let failed = 0;

        for (const jobId of jobIds) {
            try {
                const job = await this.queue.getJob(jobId);
                if (job) {
                    const state = await job.getState();
                    // Only remove jobs that haven't been processed yet
                    if (state === 'delayed' || state === 'waiting') {
                        await job.remove();
                        removed++;
                        console.log(`[Queue] Removed job ${jobId} (was ${state})`);
                    } else {
                        console.log(`[Queue] Job ${jobId} already in state '${state}', skipping removal`);
                    }
                } else {
                    console.log(`[Queue] Job ${jobId} not found in queue (already processed or expired)`);
                }
            } catch (error) {
                console.error(`[Queue] Failed to remove job ${jobId}:`, error);
                failed++;
            }
        }

        console.log(`[Queue] Removal summary: ${removed} removed, ${failed} failed out of ${jobIds.length} total`);
        return { removed, failed };
    }

    /**
     * Add a single job to the delay queue.
     */
    private async addJob(data: NotificationJobData, delay: number): Promise<Job | undefined> {
        try {
            const job = await this.queue.add(data.purpose, data, {
                delay,
                attempts: 3,
                backoff: {
                    type: 'exponential',
                    delay: 5000   // 5s → 10s → 20s
                },
                removeOnComplete: true,
                removeOnFail: false
            });

            console.log(`[Queue] Added job ${job.id} | ${data.purpose} | delay: ${Math.round(delay / 1000)}s`);
            return job;
        } catch (error) {
            console.error(`[Queue] Failed to add job for ${data.purpose}:`, error);
            return undefined;
        }
    }
}

export default new NotificationQueueService();
