import { Queue, Worker, type Job } from 'bullmq';
import { env } from '../config/env';
import { prisma } from '../config/database';
import { logger } from '../utils/logger';

interface PollAutoCloseJobData {
  pollId: string;
}

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

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

  startWorker() {
    this.worker = new Worker(
      'straw-poll-auto-close',
      async (job: Job<PollAutoCloseJobData>) => {
        const { pollId } = job.data;
        logger.info(`Processing straw poll auto-close job ${job.id}`, { pollId });

        // Dynamic import to avoid circular dependency
        const { strawPollsService } = await import(
          '../modules/polls/polls.service'
        );
        await strawPollsService.closePoll(pollId);
      },
      {
        connection: { url: env.REDIS_URL },
        concurrency: 1,
      }
    );

    this.worker.on('completed', (job) => {
      logger.info(`Straw poll auto-close job ${job.id} completed`);
    });

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

    logger.info('Straw poll auto-close queue worker started');

    this.recoverOnStartup().catch((err) =>
      logger.error('Straw poll auto-close startup recovery failed', { error: err })
    );
  }

  async scheduleJob(pollId: string, deadline: Date): Promise<string | null> {
    const delay = deadline.getTime() - Date.now();
    if (delay <= 0) {
      const job = await this.queue.add(`close-${pollId}`, { pollId }, {
        jobId: `poll-close-${pollId}`,
      });
      return job.id ?? null;
    }

    const job = await this.queue.add(`close-${pollId}`, { pollId }, {
      delay,
      jobId: `poll-close-${pollId}`,
    });
    logger.info(`Scheduled straw poll auto-close for ${deadline.toISOString()}`, {
      pollId,
      jobId: job.id,
      delayMs: delay,
    });
    return job.id ?? null;
  }

  async cancelJob(pollId: string): Promise<void> {
    try {
      const jobs = await this.queue.getJobs(['delayed', 'waiting']);
      for (const job of jobs) {
        if (job.data.pollId === pollId) {
          await job.remove();
          logger.info(`Cancelled auto-close job for straw poll ${pollId}`, { jobId: job.id });
        }
      }
    } catch (error) {
      logger.error('Failed to cancel straw poll auto-close job', { error, pollId });
    }
  }

  private async recoverOnStartup() {
    const activePolls = await prisma.strawPoll.findMany({
      where: {
        status: 'ACTIVE',
        closesAt: { not: null },
      },
      select: { id: true, closesAt: true },
    });

    for (const poll of activePolls) {
      if (!poll.closesAt) continue;
      const jobId = await this.scheduleJob(poll.id, poll.closesAt);
      if (jobId) {
        await prisma.strawPoll.update({
          where: { id: poll.id },
          data: { autoCloseJobId: jobId },
        }).catch(() => {});
      }
    }

    if (activePolls.length > 0) {
      logger.info(`Recovered ${activePolls.length} straw poll auto-close jobs on startup`);
    }
  }

  async close() {
    if (this.worker) {
      await this.worker.close();
    }
    await this.queue.close();
    logger.info('Straw poll auto-close queue closed');
  }
}

export const pollAutoCloseQueueService = new PollAutoCloseQueueService();
