import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { AppLoggerService } from './logger.service';
import { BROWSER_MANAGER_PAGES_CLEANUP_THRESHOLD } from 'src/common/constants/constants';
import { handleKnownErrors } from 'src/common/utils/handle-error.util';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';
const puppeteer = require('puppeteer-core');
import chromium from '@sparticuz/chromium-min';
import type { Browser } from 'puppeteer-core';

/**
 * Browser Manager Service
 * Manages a singleton Chromium browser instance that is launched on server startup
 * and reused for all PDF generation requests to improve performance.
 */
@Injectable()
export class BrowserManagerService implements OnModuleInit, OnModuleDestroy {
  private browser: Browser | null = null;
  private isLaunching = false;
  private launchPromise: Promise<Browser> | null = null;

  constructor(private readonly logger: AppLoggerService) {}

  /**
   * Launch browser on module initialization (server startup)
   */
  async onModuleInit() {
    this.logger.log('Initializing Browser Manager Service...');
    try {
      await this.launchBrowser();
      this.logger.log('Browser launched successfully on startup');
    } catch (error) {
      this.logger.error('Failed to launch browser on startup', '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
      });
      // Don't throw error - allow server to start even if browser fails
      // Browser will be launched on-demand when needed
    }
  }

  /**
   * Close browser on module destruction (server shutdown)
   */
  async onModuleDestroy() {
    this.logger.log('Shutting down Browser Manager Service...');
    await this.closeBrowser();
  }

  /**
   * Launch the Chromium browser instance
   */
  private async launchBrowser(): Promise<Browser> {
    // If already launching, wait for that process to complete
    if (this.isLaunching && this.launchPromise) {
      this.logger.debug('Browser launch already in progress, waiting...');
      return this.launchPromise;
    }

    // If browser already exists and is connected, return it
    if (this.browser && this.browser.isConnected()) {
      this.logger.debug('Browser already launched and connected');
      return this.browser;
    }

    this.isLaunching = true;
    this.launchPromise = (async (): Promise<Browser> => {
      try {
        this.logger.log('Launching Chromium browser...');
        
        const executablePath = await chromium.executablePath(
          'https://github.com/Sparticuz/chromium/releases/download/v119.0.2/chromium-v119.0.2-pack.tar',
        );

        const browser = await puppeteer.launch({
          args: [
            ...(Array.isArray(chromium.args) ? chromium.args : []),
            '--no-sandbox',
            '--disable-setuid-sandbox',
            // Use /tmp instead of the tiny /dev/shm most containers give — prevents the
            // renderer from crashing (which surfaces as "Navigating frame was detached").
            '--disable-dev-shm-usage',
            '--disable-features=AudioServiceOutOfProcess',
            '--disable-gpu',
            '--disable-features=AudioServiceOutOfProcessKillAtHang',
            // NOTE: --single-process removed — it made a renderer crash kill the whole browser.
            '--disable-software-rasterizer',
          ],
          defaultViewport: null,
          executablePath,
          headless: true,
        }) as Browser;

        this.browser = browser;

        // Handle browser disconnect events
        browser.on('disconnected', () => {
          this.logger.warn('Browser disconnected unexpectedly');
          this.browser = null;
        });

        this.logger.log('Chromium browser launched successfully');
        return browser;
      } catch (error) {
        this.logger.error('Error launching browser', '', {
          error: error instanceof Error ? error.message : String(error),
          stack: error instanceof Error ? error.stack : undefined,
        });
        this.browser = null;
        handleKnownErrors(ERROR_CODES.INTERNAL_SERVER_ERROR, error);
      } finally {
        this.isLaunching = false;
        this.launchPromise = null;
      }
    })();

    return this.launchPromise;
  }

  /**
   * Close the browser instance
   */
  private async closeBrowser(): Promise<void> {
    if (this.browser) {
      try {
        this.logger.log('Closing Chromium browser...');
        await this.browser.close();
        this.browser = null;
        this.logger.log('Browser closed successfully');
      } catch (error) {
        this.logger.error('Error closing browser', '', {
          error: error instanceof Error ? error.message : String(error),
          stack: error instanceof Error ? error.stack : undefined,
        });
        this.browser = null;
      }
    }
  }

  /**
   * Get the browser instance, launching it if necessary
   * This ensures the browser is always available when needed
   * If browser launch failed on startup, this will retry the launch
   */
  async getBrowser(): Promise<Browser> {
    // Check if browser exists and is connected
    if (this.browser && this.browser.isConnected()) {
      return this.browser;
    }

    // Browser doesn't exist or is disconnected, launch it
    this.logger.warn('Browser not available, launching new instance...');
    return await this.launchBrowser();
  }

  /**
   * Check if browser is currently connected
   */
  isConnected(): boolean {
    return !!this.browser && this.browser.isConnected();
  }

  /**
   * Cleanup browser resources after processing batches
   * Call this periodically to prevent memory leaks
   * 
   * Following coding standard: Resource Management (AGENTS.md §9)
   */
  async cleanupBrowserResources(): Promise<void> {
    try {
      const browser = await this.getBrowser();
      const pages = await browser.pages();
      
      // Keep at least 1 page, close extras
      if (pages.length > BROWSER_MANAGER_PAGES_CLEANUP_THRESHOLD) {
        this.logger.log(`Cleaning up ${pages.length - 1} idle browser pages`);
        
        for (let i = 1; i < pages.length; i++) {
          try {
            await pages[i].close();
          } catch (error) {
            this.logger.warn(`Failed to close page ${i}`, {
              error: error instanceof Error ? error.message : String(error),
              stack: error instanceof Error ? error.stack : undefined,
            });
          }
        }
      }
    } catch (error) {
      this.logger.error('Browser cleanup failed', '', {
        error: error instanceof Error ? error.message : String(error),
        stack: error instanceof Error ? error.stack : undefined,
      });
    }
  }
}
