import { Injectable, Logger } from '@nestjs/common';
import { IntegrationType } from '@prisma/client';
import { IntegrationSyncProcessor, SyncJobData } from './integration-sync.processor';

/**
 * Queue abstraction for integration sync jobs
 *
 * This provides a simple queue interface that can be used with or without BullMQ.
 * When Redis is configured, this could be upgraded to use actual BullMQ queues.
 *
 * Features:
 * - Job scheduling with delay
 * - Priority queuing
 * - Job tracking
 */
@Injectable()
export class IntegrationSyncQueue {
  private readonly logger = new Logger(IntegrationSyncQueue.name);
  private readonly pendingJobs = new Map<
    string,
    {
      data: SyncJobData;
      priority: number;
      scheduledAt: Date;
      timeout?: NodeJS.Timeout;
    }
  >();
  private jobIdCounter = 0;

  constructor(private readonly processor: IntegrationSyncProcessor) {}

  /**
   * Adds a sync job to the queue
   *
   * @param data Job data
   * @param options Job options
   * @returns Job ID
   */
  async add(
    data: SyncJobData,
    options?: {
      delay?: number;
      priority?: number;
      jobId?: string;
    },
  ): Promise<string> {
    const jobId = options?.jobId || `sync-${++this.jobIdCounter}-${Date.now()}`;
    const delay = options?.delay || 0;
    const priority = options?.priority || 0;

    this.logger.debug(
      `Adding job ${jobId} for ${data.integrationType}:${data.integrationId} ` +
        `with delay ${delay}ms and priority ${priority}`,
    );

    if (delay > 0) {
      // Schedule for later
      const timeout = setTimeout(async () => {
        this.pendingJobs.delete(jobId);
        await this.processor.processJob(data);
      }, delay);

      this.pendingJobs.set(jobId, {
        data,
        priority,
        scheduledAt: new Date(Date.now() + delay),
        timeout,
      });
    } else {
      // Process immediately
      this.pendingJobs.set(jobId, {
        data,
        priority,
        scheduledAt: new Date(),
      });

      // Process async to not block
      setImmediate(async () => {
        this.pendingJobs.delete(jobId);
        await this.processor.processJob(data);
      });
    }

    return jobId;
  }

  /**
   * Adds a bulk sync job for multiple credentials
   */
  async addBulk(
    jobs: Array<{
      data: SyncJobData;
      options?: {
        delay?: number;
        priority?: number;
      };
    }>,
  ): Promise<string[]> {
    const jobIds: string[] = [];

    for (const job of jobs) {
      const jobId = await this.add(job.data, job.options);
      jobIds.push(jobId);
    }

    return jobIds;
  }

  /**
   * Cancels a pending job
   */
  cancel(jobId: string): boolean {
    const job = this.pendingJobs.get(jobId);

    if (job) {
      if (job.timeout) {
        clearTimeout(job.timeout);
      }
      this.pendingJobs.delete(jobId);
      this.logger.debug(`Cancelled job ${jobId}`);
      return true;
    }

    return false;
  }

  /**
   * Gets the status of a job
   */
  getJobStatus(
    jobId: string,
  ): { status: 'pending' | 'scheduled' | 'unknown'; scheduledAt?: Date } {
    const job = this.pendingJobs.get(jobId);

    if (job) {
      return {
        status: job.timeout ? 'scheduled' : 'pending',
        scheduledAt: job.scheduledAt,
      };
    }

    return { status: 'unknown' };
  }

  /**
   * Gets all pending jobs
   */
  getPendingJobs(): Array<{
    jobId: string;
    data: SyncJobData;
    priority: number;
    scheduledAt: Date;
  }> {
    return Array.from(this.pendingJobs.entries()).map(([jobId, job]) => ({
      jobId,
      data: job.data,
      priority: job.priority,
      scheduledAt: job.scheduledAt,
    }));
  }

  /**
   * Clears all pending jobs
   */
  clear(): number {
    let count = 0;

    for (const [jobId, job] of this.pendingJobs) {
      if (job.timeout) {
        clearTimeout(job.timeout);
      }
      count++;
    }

    this.pendingJobs.clear();
    this.logger.log(`Cleared ${count} pending jobs`);
    return count;
  }

  /**
   * Gets queue statistics
   */
  getStats(): {
    pendingCount: number;
    scheduledCount: number;
  } {
    let pendingCount = 0;
    let scheduledCount = 0;

    for (const job of this.pendingJobs.values()) {
      if (job.timeout) {
        scheduledCount++;
      } else {
        pendingCount++;
      }
    }

    return { pendingCount, scheduledCount };
  }

  /**
   * Schedules a sync for all active credentials of a specific type
   */
  async scheduleTypeSync(
    type: IntegrationType,
    options?: { fullSync?: boolean; stagger?: number },
  ): Promise<string[]> {
    // This would fetch all credentials of the given type and schedule syncs
    // For now, this is a placeholder that would need to be connected to the database
    this.logger.log(
      `Scheduling sync for all ${type} integrations (fullSync: ${options?.fullSync || false})`,
    );

    return [];
  }
}
