import { Injectable, Inject, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { ConfigService } from "@nestjs/config";

type PQueueCtor = new (options: {
  concurrency?: number;
  interval?: number;
  intervalCap?: number;
  timeout?: number;
  throwOnTimeout?: boolean;
  carryoverConcurrencyCount?: boolean;
}) => PQueueInstance;

interface PQueueInstance {
  add<T>(fn: () => Promise<T>, options?: { priority?: number }): Promise<T>;
  clear(): void;
  onIdle(): Promise<void>;
  on(event: "add" | "next" | "idle" | "error", listener: (...args: unknown[]) => void): void;
  size: number;
  pending: number;
}
export interface WhmcsQueueMetrics {
  totalRequests: number;
  completedRequests: number;
  failedRequests: number;
  queueSize: number;
  pendingRequests: number;
  averageWaitTime: number;
  averageExecutionTime: number;
  lastRequestTime?: Date;
  lastErrorTime?: Date;
}

export interface WhmcsRequestOptions {
  priority?: number; // Higher number = higher priority (0-10)
  timeout?: number; // Request timeout in ms
  retryAttempts?: number; // Number of retry attempts
  retryDelay?: number; // Base delay between retries in ms
}

/**
 * WHMCS Request Queue Service
 *
 * Manages concurrent requests to WHMCS API to prevent:
 * - Database connection pool exhaustion
 * - Server overload from parallel requests
 * - Rate limit violations (conservative approach)
 * - Resource contention issues
 *
 * Based on research:
 * - WHMCS has no official rate limits but performance degrades with high concurrency
 * - Conservative approach: max 3 concurrent requests
 * - Rate limiting: max 30 requests per minute (0.5 RPS)
 */
@Injectable()
export class WhmcsRequestQueueService implements OnModuleInit, OnModuleDestroy {
  private pQueueCtor: PQueueCtor | null = null;
  private queue: PQueueInstance | null = null;
  private readonly metrics: WhmcsQueueMetrics = {
    totalRequests: 0,
    completedRequests: 0,
    failedRequests: 0,
    queueSize: 0,
    pendingRequests: 0,
    averageWaitTime: 0,
    averageExecutionTime: 0,
  };

  private readonly waitTimes: number[] = [];
  private readonly executionTimes: number[] = [];
  private readonly maxMetricsHistory = 100;

  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly configService: ConfigService
  ) {}

  private async loadPQueue(): Promise<PQueueCtor> {
    if (!this.pQueueCtor) {
      const module = await import("p-queue");
      this.pQueueCtor = module.default as PQueueCtor;
    }
    return this.pQueueCtor;
  }

  private async ensureQueueInitialized(): Promise<PQueueInstance> {
    if (!this.queue) {
      const PQueue = await this.loadPQueue();
      const concurrency = this.configService.get<number>("WHMCS_QUEUE_CONCURRENCY", 15);
      const intervalCap = this.configService.get<number>("WHMCS_QUEUE_INTERVAL_CAP", 300);
      const timeout = this.configService.get<number>("WHMCS_QUEUE_TIMEOUT_MS", 30000);

      // Configurable WHMCS queue configuration
      this.queue = new PQueue({
        concurrency, // Max concurrent WHMCS requests
        interval: 60000, // Per minute
        intervalCap, // Max requests per minute
        timeout, // Request timeout
        throwOnTimeout: true,
        carryoverConcurrencyCount: true,
      });

      // Set up queue event listeners
      this.setupQueueListeners();
    }

    if (!this.queue) {
      throw new Error("Failed to initialize WHMCS queue");
    }

    return this.queue;
  }

  async onModuleInit() {
    await this.ensureQueueInitialized();
    const concurrency = this.configService.get<number>("WHMCS_QUEUE_CONCURRENCY", 15);
    const intervalCap = this.configService.get<number>("WHMCS_QUEUE_INTERVAL_CAP", 300);
    const timeout = this.configService.get<number>("WHMCS_QUEUE_TIMEOUT_MS", 30000);

    this.logger.log("WHMCS Request Queue initialized", {
      concurrency,
      rateLimit: `${intervalCap} requests/minute (${(intervalCap / 60).toFixed(1)} RPS)`,
      timeout: `${timeout / 1000} seconds`,
    });
  }

  async onModuleDestroy() {
    this.logger.log("Shutting down WHMCS Request Queue", {
      pendingRequests: this.queue?.pending ?? 0,
      queueSize: this.queue?.size ?? 0,
    });

    // Wait for pending requests to complete (with timeout)
    try {
      await this.queue?.onIdle();
    } catch (error) {
      this.logger.warn("Some WHMCS requests may not have completed during shutdown", {
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }

  /**
   * Execute a WHMCS API request through the queue
   */
  async execute<T>(requestFn: () => Promise<T>, options: WhmcsRequestOptions = {}): Promise<T> {
    const queue = await this.ensureQueueInitialized();
    const startTime = Date.now();
    const requestId = this.generateRequestId();

    this.metrics.totalRequests++;
    this.updateQueueMetrics();

    this.logger.debug("Queueing WHMCS request", {
      requestId,
      queueSize: queue.size,
      pending: queue.pending,
      priority: options.priority || 0,
    });

    try {
      const result = (await queue.add(
        async () => {
          const waitTime = Date.now() - startTime;
          this.recordWaitTime(waitTime);

          const executionStart = Date.now();

          try {
            const response = await this.executeWithRetry(requestFn, options);

            const executionTime = Date.now() - executionStart;
            this.recordExecutionTime(executionTime);
            this.metrics.completedRequests++;
            this.metrics.lastRequestTime = new Date();

            this.logger.debug("WHMCS request completed", {
              requestId,
              waitTime,
              executionTime,
              totalTime: Date.now() - startTime,
            });

            return response;
          } catch (error) {
            const executionTime = Date.now() - executionStart;
            this.recordExecutionTime(executionTime);
            this.metrics.failedRequests++;
            this.metrics.lastErrorTime = new Date();

            this.logger.error("WHMCS request failed", {
              requestId,
              waitTime,
              executionTime,
              error: error instanceof Error ? error.message : String(error),
            });

            throw error;
          }
        },
        {
          priority: options.priority ?? 0,
        }
      )) as T;

      return result;
    } catch (error) {
      this.metrics.failedRequests++;
      this.metrics.lastErrorTime = new Date();
      throw error;
    } finally {
      this.updateQueueMetrics();
    }
  }

  /**
   * Execute high-priority WHMCS request (jumps queue)
   */
  async executeHighPriority<T>(requestFn: () => Promise<T>): Promise<T> {
    return this.execute(requestFn, { priority: 10 });
  }

  /**
   * Get current queue metrics
   */
  getMetrics(): WhmcsQueueMetrics {
    this.updateQueueMetrics();
    return { ...this.metrics };
  }

  /**
   * Get queue health status
   */
  getHealthStatus(): {
    status: "healthy" | "degraded" | "unhealthy";
    queueSize: number;
    pendingRequests: number;
    errorRate: number;
    averageWaitTime: number;
  } {
    this.updateQueueMetrics();

    const errorRate =
      this.metrics.totalRequests > 0 ? this.metrics.failedRequests / this.metrics.totalRequests : 0;

    let status: "healthy" | "degraded" | "unhealthy" = "healthy";

    // Adjusted thresholds for higher throughput (15 concurrent, 5 RPS)
    if (this.metrics.queueSize > 120 || errorRate > 0.1) {
      status = "unhealthy";
    } else if (this.metrics.queueSize > 50 || errorRate > 0.05) {
      status = "degraded";
    }

    return {
      status,
      queueSize: this.metrics.queueSize,
      pendingRequests: this.metrics.pendingRequests,
      errorRate,
      averageWaitTime: this.metrics.averageWaitTime,
    };
  }

  /**
   * Clear the queue (emergency use only)
   */
  async clearQueue(): Promise<void> {
    const queue = this.queue;

    if (!queue) {
      return;
    }

    this.logger.warn("Clearing WHMCS request queue", {
      queueSize: queue.size,
      pendingRequests: queue.pending,
    });

    queue.clear();
    await queue.onIdle();
  }

  private async executeWithRetry<T>(
    requestFn: () => Promise<T>,
    options: WhmcsRequestOptions
  ): Promise<T> {
    const maxAttempts = options.retryAttempts || 3;
    const baseDelay = options.retryDelay || 1000;

    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        const result = await requestFn();
        return result;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));

        if (attempt === maxAttempts) {
          break;
        }

        // Exponential backoff with jitter
        const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000;

        this.logger.debug("WHMCS request failed, retrying", {
          attempt,
          maxAttempts,
          delay,
          error: lastError.message,
        });

        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }

    throw lastError instanceof Error ? lastError : new Error(String(lastError));
  }

  private setupQueueListeners(): void {
    if (!this.queue) {
      return;
    }

    this.queue.on("add", () => {
      this.updateQueueMetrics();
    });

    this.queue.on("next", () => {
      this.updateQueueMetrics();
    });

    this.queue.on("idle", () => {
      this.logger.debug("WHMCS queue is idle");
      this.updateQueueMetrics();
    });

    this.queue.on("error", (error: unknown) => {
      this.logger.error("WHMCS queue error", {
        error: error instanceof Error ? error.message : String(error),
      });
    });
  }

  private updateQueueMetrics(): void {
    if (!this.queue) {
      return;
    }

    this.metrics.queueSize = this.queue.size;
    this.metrics.pendingRequests = this.queue.pending;

    // Calculate averages
    if (this.waitTimes.length > 0) {
      this.metrics.averageWaitTime =
        this.waitTimes.reduce((sum, time) => sum + time, 0) / this.waitTimes.length;
    }

    if (this.executionTimes.length > 0) {
      this.metrics.averageExecutionTime =
        this.executionTimes.reduce((sum, time) => sum + time, 0) / this.executionTimes.length;
    }
  }

  private recordWaitTime(waitTime: number): void {
    this.waitTimes.push(waitTime);
    if (this.waitTimes.length > this.maxMetricsHistory) {
      this.waitTimes.shift();
    }
  }

  private recordExecutionTime(executionTime: number): void {
    this.executionTimes.push(executionTime);
    if (this.executionTimes.length > this.maxMetricsHistory) {
      this.executionTimes.shift();
    }
  }

  private generateRequestId(): string {
    return `whmcs_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}
