import { Queue, Worker, type Job } from 'bullmq';
import { env } from '../config/env';
import { logger } from '../utils/logger';
import { emailService } from './email.service';
import { prisma } from '../config/database';
import { notificationService } from '../modules/social/notification.service';

// ─── Job Data Types ────────────────────────────────────────────────

interface AdminShiftSignupJob {
  type: 'admin-shift-signup';
  adminEmails: string[];
  shiftTitle: string;
  shiftDate: string;
  volunteerName: string;
  volunteerEmail: string;
  signupSource: string;
  adminUrl: string;
}

interface AdminResponseSubmittedJob {
  type: 'admin-response-submitted';
  adminEmails: string[];
  campaignTitle: string;
  representativeName: string;
  responseType: string;
  submitterName: string;
  adminUrl: string;
}

interface AdminSignRequestedJob {
  type: 'admin-sign-requested';
  adminEmails: string[];
  volunteerName: string;
  address: string;
  shiftTitle: string;
  signSize: string;
  adminUrl: string;
}

interface AdminShiftCancellationJob {
  type: 'admin-shift-cancellation';
  adminEmails: string[];
  shiftTitle: string;
  shiftDate: string;
  volunteerName: string;
  volunteerEmail: string;
  cancellationSource: string;
  adminUrl: string;
}

interface VolunteerSessionSummaryJob {
  type: 'volunteer-session-summary';
  volunteerEmail: string;
  volunteerName: string;
  cutName: string;
  sessionDate: string;
  visitCount: number;
  durationMinutes: number;
  distanceKm: number;
  outcomeBreakdown: Record<string, number>;
}

interface VolunteerCancellationJob {
  type: 'volunteer-cancellation';
  volunteerEmail: string;
  volunteerName: string;
  shiftTitle: string;
  shiftDate: string;
  shiftTime: string;
  signupUrl: string;
}

interface VolunteerShiftReminderJob {
  type: 'volunteer-shift-reminder';
  recipientEmail: string;
  recipientName: string;
  shiftTitle: string;
  shiftDate: string;
  shiftStartTime: string;
  shiftEndTime: string;
  shiftLocation: string;
  shiftDescription: string;
  currentVolunteers: number;
  maxVolunteers: number;
  shiftStatus: string;
}

interface VolunteerShiftThankYouJob {
  type: 'volunteer-shift-thank-you';
  volunteerEmail: string;
  volunteerName: string;
  shiftTitle: string;
  shiftDate: string;
  shiftTime: string;
  shiftLocation: string;
  signupUrl: string;
}

interface VolunteerReengagementJob {
  type: 'volunteer-reengagement';
  volunteerEmail: string;
  volunteerName: string;
  lastActivityDate: string;
  lastActivityType: string;
  signupUrl: string;
}

type NotificationJobData =
  | AdminShiftSignupJob
  | AdminResponseSubmittedJob
  | AdminSignRequestedJob
  | AdminShiftCancellationJob
  | VolunteerSessionSummaryJob
  | VolunteerCancellationJob
  | VolunteerShiftReminderJob
  | VolunteerShiftThankYouJob
  | VolunteerReengagementJob;

// ─── Queue Service ─────────────────────────────────────────────────

/** Resolve userId from email for in-app notification bridging */
async function resolveUserId(email: string): Promise<string | null> {
  const user = await prisma.user.findUnique({ where: { email }, select: { id: true } });
  return user?.id ?? null;
}

/** Fire-and-forget in-app notification creation */
function bridgeToInApp(
  email: string,
  type: 'shift_signup_confirmed' | 'shift_reminder' | 'shift_cancelled' | 'canvass_session_summary' | 'reengagement',
  title: string,
  message: string,
  metadata?: Record<string, unknown>,
) {
  resolveUserId(email).then((userId) => {
    if (!userId) return;
    notificationService.createNotification(userId, type, title, message, metadata);
  }).catch((err) => logger.warn('Failed to bridge in-app notification', err));
}

class NotificationQueueService {
  private queue: Queue;
  private worker: Worker | null = null;

  constructor() {
    this.queue = new Queue('notification-emails', {
      connection: { url: env.REDIS_URL },
      defaultJobOptions: {
        attempts: 3,
        backoff: { type: 'exponential', delay: 5000 },
        removeOnComplete: { age: 24 * 60 * 60, count: 500 },
        removeOnFail: { age: 7 * 24 * 60 * 60 },
      },
    });
  }

  startWorker() {
    this.worker = new Worker(
      'notification-emails',
      async (job: Job<NotificationJobData>) => {
        const { data } = job;
        logger.info(`Processing notification job ${job.id} type=${data.type}`);

        switch (data.type) {
          case 'admin-shift-signup':
            await emailService.sendAdminShiftSignupAlert(data);
            break;
          case 'admin-response-submitted':
            await emailService.sendAdminResponseSubmittedAlert(data);
            break;
          case 'admin-sign-requested':
            await emailService.sendAdminSignRequestedAlert(data);
            break;
          case 'admin-shift-cancellation':
            await emailService.sendAdminShiftCancellationAlert(data);
            break;
          case 'volunteer-session-summary':
            await emailService.sendVolunteerSessionSummary(data);
            bridgeToInApp(
              data.volunteerEmail, 'canvass_session_summary',
              'Canvass Session Complete',
              `You visited ${data.visitCount} addresses in ${data.cutName}`,
              { cutName: data.cutName, visitCount: data.visitCount, durationMinutes: data.durationMinutes },
            );
            break;
          case 'volunteer-cancellation':
            await emailService.sendVolunteerCancellationAck(data);
            bridgeToInApp(
              data.volunteerEmail, 'shift_cancelled',
              'Shift Cancelled',
              `Your shift "${data.shiftTitle}" on ${data.shiftDate} has been cancelled`,
              { shiftTitle: data.shiftTitle, shiftDate: data.shiftDate },
            );
            break;
          case 'volunteer-shift-reminder':
            await emailService.sendShiftDetailsEmail({
              recipientEmail: data.recipientEmail,
              recipientName: data.recipientName,
              shiftTitle: data.shiftTitle,
              shiftDate: data.shiftDate,
              shiftStartTime: data.shiftStartTime,
              shiftEndTime: data.shiftEndTime,
              shiftLocation: data.shiftLocation,
              shiftDescription: data.shiftDescription,
              currentVolunteers: data.currentVolunteers,
              maxVolunteers: data.maxVolunteers,
              shiftStatus: data.shiftStatus,
            });
            bridgeToInApp(
              data.recipientEmail, 'shift_reminder',
              'Shift Reminder',
              `Reminder: "${data.shiftTitle}" on ${data.shiftDate} at ${data.shiftStartTime}`,
              { shiftTitle: data.shiftTitle, shiftDate: data.shiftDate, shiftLocation: data.shiftLocation },
            );
            break;
          case 'volunteer-shift-thank-you':
            await emailService.sendVolunteerShiftThankYou(data);
            break;
          case 'volunteer-reengagement':
            await emailService.sendVolunteerReengagement(data);
            break;
        }
      },
      {
        connection: { url: env.REDIS_URL },
        concurrency: 2,
      },
    );

    this.worker.on('completed', (job) => {
      logger.info(`Notification job ${job.id} completed`);
    });

    this.worker.on('failed', (job, err) => {
      logger.error(`Notification job ${job?.id} failed: ${err.message}`);
    });

    logger.info('Notification queue worker started');
  }

  /** Enqueue an immediate notification job. */
  async enqueue(data: NotificationJobData): Promise<string> {
    const job = await this.queue.add(data.type, data);
    return job.id!;
  }

  /**
   * Schedule a shift reminder as a delayed job.
   * Uses a deterministic jobId so we can cancel it later.
   */
  async scheduleShiftReminder(
    data: VolunteerShiftReminderJob,
    shiftDatetime: Date,
  ): Promise<string | null> {
    const reminderTime = new Date(shiftDatetime.getTime() - 24 * 60 * 60 * 1000);
    const delay = reminderTime.getTime() - Date.now();

    if (delay <= 0) {
      logger.debug('Shift is less than 24h away, skipping reminder scheduling');
      return null;
    }

    const jobId = `shift-reminder-${data.recipientEmail}-${shiftDatetime.getTime()}`;
    const job = await this.queue.add(data.type, data, {
      delay,
      jobId,
    });
    logger.info(`Scheduled shift reminder jobId=${jobId} delay=${Math.round(delay / 60000)}min`);
    return job.id!;
  }

  /**
   * Schedule a post-shift thank-you email as a delayed job.
   * Fires 2 hours after shift end time. Uses deterministic jobId for cancellation.
   */
  async scheduleShiftThankYou(
    data: VolunteerShiftThankYouJob,
    shiftEndDatetime: Date,
  ): Promise<string | null> {
    const thankYouTime = new Date(shiftEndDatetime.getTime() + 2 * 60 * 60 * 1000);
    const delay = thankYouTime.getTime() - Date.now();

    if (delay <= 0) {
      logger.debug('Shift has already ended 2h+ ago, skipping thank-you scheduling');
      return null;
    }

    const jobId = `shift-thankyou-${data.volunteerEmail}-${shiftEndDatetime.getTime()}`;
    const job = await this.queue.add(data.type, data, {
      delay,
      jobId,
    });
    logger.info(`Scheduled shift thank-you jobId=${jobId} delay=${Math.round(delay / 60000)}min`);
    return job.id!;
  }

  /** Cancel a pending shift thank-you. */
  async cancelShiftThankYou(email: string, shiftEndDatetime: Date): Promise<void> {
    const jobId = `shift-thankyou-${email}-${shiftEndDatetime.getTime()}`;
    try {
      const job = await this.queue.getJob(jobId);
      if (job) {
        await job.remove();
        logger.info(`Cancelled shift thank-you jobId=${jobId}`);
      }
    } catch (err) {
      logger.warn(`Failed to cancel shift thank-you jobId=${jobId}:`, err);
    }
  }

  /** Cancel a pending shift reminder. */
  async cancelShiftReminder(email: string, shiftDatetime: Date): Promise<void> {
    const jobId = `shift-reminder-${email}-${shiftDatetime.getTime()}`;
    try {
      const job = await this.queue.getJob(jobId);
      if (job) {
        await job.remove();
        logger.info(`Cancelled shift reminder jobId=${jobId}`);
      }
    } catch (err) {
      logger.warn(`Failed to cancel shift reminder jobId=${jobId}:`, err);
    }
  }

  async close() {
    if (this.worker) {
      await this.worker.close();
    }
    await this.queue.close();
    logger.info('Notification queue closed');
  }
}

export const notificationQueueService = new NotificationQueueService();
