import { BrowserSession } from './BrowserSession';
import {
  IBrowserPool,
  IBrowserSession,
  SessionConfig,
  PoolStats,
} from '../types';
import { createLogger } from '../utils/logger';
import { config } from '../config';
import {
  sessionsCreatedCounter,
  sessionsClosedCounter,
  activeSessionsGauge,
  browserPoolSizeGauge,
  sessionMemoryHistogram,
} from '../utils/metrics';

const logger = createLogger('BrowserPool');

/**
 * BrowserPool manages a pool of browser sessions
 * Handles session lifecycle, cleanup, and resource management
 */
export class BrowserPool implements IBrowserPool {
  private sessions: Map<string, IBrowserSession> = new Map();
  private queue: Array<{
    config: SessionConfig;
    resolve: (session: IBrowserSession) => void;
    reject: (error: Error) => void;
  }> = [];
  private cleanupInterval: NodeJS.Timeout | null = null;
  private isShuttingDown = false;

  constructor() {
    this.startCleanupTask();
    logger.info('BrowserPool initialized', {
      maxSessions: config.browserPool.maxConcurrentSessions,
      idleTimeout: config.browserPool.idleTimeout,
    });
  }

  /**
   * Get or create a browser session
   */
  async getSession(sessionConfig: SessionConfig): Promise<IBrowserSession> {
    // Check if session already exists
    if (this.sessions.has(sessionConfig.sessionId)) {
      const session = this.sessions.get(sessionConfig.sessionId)!;
      logger.info(`Reusing existing session: ${sessionConfig.sessionId}`);
      return session;
    }

    // Check if we've reached the concurrent session limit
    if (this.sessions.size >= config.browserPool.maxConcurrentSessions) {
      logger.warn(`Session limit reached, queuing request`, {
        currentSessions: this.sessions.size,
        maxSessions: config.browserPool.maxConcurrentSessions,
      });

      // Queue the request
      return new Promise<IBrowserSession>((resolve, reject) => {
        this.queue.push({ config: sessionConfig, resolve, reject });

        // Set timeout for queued request
        setTimeout(() => {
          const index = this.queue.findIndex((q) => q.config.sessionId === sessionConfig.sessionId);
          if (index !== -1) {
            this.queue.splice(index, 1);
            reject(new Error('Session request timeout - pool is full'));
          }
        }, 30000); // 30 second timeout
      });
    }

    // Create new session
    return this.createSession(sessionConfig);
  }

  /**
   * Create a new browser session
   */
  private async createSession(sessionConfig: SessionConfig): Promise<IBrowserSession> {
    try {
      logger.info(`Creating new browser session: ${sessionConfig.sessionId}`, {
        userId: sessionConfig.userId,
        deviceMode: sessionConfig.deviceMode,
      });

      const session = await BrowserSession.create(sessionConfig);

      // Store session
      this.sessions.set(sessionConfig.sessionId, session);

      // Update metrics
      sessionsCreatedCounter.inc({ device_mode: sessionConfig.deviceMode });
      activeSessionsGauge.set(this.sessions.size);
      browserPoolSizeGauge.set(this.sessions.size);

      // Measure memory usage (approximate)
      if (process.memoryUsage) {
        const memUsage = process.memoryUsage();
        sessionMemoryHistogram.observe(memUsage.heapUsed / this.sessions.size);
      }

      logger.info(`Browser session created successfully: ${sessionConfig.sessionId}`);

      return session;
    } catch (error) {
      logger.error(`Failed to create browser session: ${sessionConfig.sessionId}`, {
        error: error instanceof Error ? error.message : String(error),
      });
      throw error;
    }
  }

  /**
   * Release a session back to the pool (mark as idle but don't close)
   */
  async releaseSession(sessionId: string): Promise<void> {
    const session = this.sessions.get(sessionId);
    if (!session) {
      logger.warn(`Attempted to release non-existent session: ${sessionId}`);
      return;
    }

    logger.info(`Session released: ${sessionId}`);

    // Process queue if there are pending requests
    this.processQueue();
  }

  /**
   * Close and remove a session from the pool
   */
  async closeSession(sessionId: string): Promise<void> {
    const session = this.sessions.get(sessionId);
    if (!session) {
      logger.warn(`Attempted to close non-existent session: ${sessionId}`);
      return;
    }

    try {
      logger.info(`Closing session: ${sessionId}`);

      await session.close();
      this.sessions.delete(sessionId);

      // Update metrics
      sessionsClosedCounter.inc({ reason: 'manual' });
      activeSessionsGauge.set(this.sessions.size);
      browserPoolSizeGauge.set(this.sessions.size);

      // Process queue if there are pending requests
      this.processQueue();

      logger.info(`Session closed successfully: ${sessionId}`);
    } catch (error) {
      logger.error(`Error closing session: ${sessionId}`, {
        error: error instanceof Error ? error.message : String(error),
      });
      // Remove from sessions even if close failed
      this.sessions.delete(sessionId);
      activeSessionsGauge.set(this.sessions.size);
      browserPoolSizeGauge.set(this.sessions.size);
    }
  }

  /**
   * Process queued session requests
   */
  private processQueue(): void {
    while (
      this.queue.length > 0 &&
      this.sessions.size < config.browserPool.maxConcurrentSessions
    ) {
      const queued = this.queue.shift();
      if (queued) {
        this.createSession(queued.config)
          .then(queued.resolve)
          .catch(queued.reject);
      }
    }
  }

  /**
   * Get pool statistics
   */
  getStats(): PoolStats {
    let activeSessions = 0;
    let idleSessions = 0;

    this.sessions.forEach((session) => {
      if (session instanceof BrowserSession) {
        if (session.isIdle()) {
          idleSessions++;
        } else {
          activeSessions++;
        }
      }
    });

    return {
      totalSessions: this.sessions.size,
      activeSessions,
      idleSessions,
      queuedRequests: this.queue.length,
      totalBrowsers: this.sessions.size,
    };
  }

  /**
   * Start periodic cleanup task
   */
  private startCleanupTask(): void {
    this.cleanupInterval = setInterval(
      () => this.cleanup(),
      config.browserPool.cleanupInterval
    );

    logger.info('Cleanup task started', {
      interval: config.browserPool.cleanupInterval,
    });
  }

  /**
   * Cleanup idle and expired sessions
   */
  async cleanup(): Promise<void> {
    if (this.isShuttingDown) {
      return;
    }

    logger.debug('Running cleanup task', {
      totalSessions: this.sessions.size,
    });

    const sessionsToClose: string[] = [];

    // Find idle sessions
    this.sessions.forEach((session, sessionId) => {
      if (session instanceof BrowserSession && session.isIdle()) {
        sessionsToClose.push(sessionId);
      }
    });

    // Close idle sessions
    for (const sessionId of sessionsToClose) {
      try {
        logger.info(`Closing idle session: ${sessionId}`);
        await this.closeSession(sessionId);
        sessionsClosedCounter.inc({ reason: 'idle' });
      } catch (error) {
        logger.error(`Error during cleanup of session: ${sessionId}`, {
          error: error instanceof Error ? error.message : String(error),
        });
      }
    }

    if (sessionsToClose.length > 0) {
      logger.info(`Cleanup completed`, {
        closedSessions: sessionsToClose.length,
        remainingSessions: this.sessions.size,
      });
    }
  }

  /**
   * Shutdown the pool and close all sessions
   */
  async shutdown(): Promise<void> {
    this.isShuttingDown = true;

    logger.info('Shutting down BrowserPool', {
      activeSessions: this.sessions.size,
      queuedRequests: this.queue.length,
    });

    // Stop cleanup task
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = null;
    }

    // Reject all queued requests
    this.queue.forEach((queued) => {
      queued.reject(new Error('Pool is shutting down'));
    });
    this.queue = [];

    // Close all sessions
    const closePromises = Array.from(this.sessions.keys()).map((sessionId) =>
      this.closeSession(sessionId)
    );

    await Promise.allSettled(closePromises);

    logger.info('BrowserPool shutdown complete');
  }
}
