import {
  Browser,
  BrowserPlatform,
  detectBrowserPlatform,
  install,
  resolveBuildId
} from '@puppeteer/browsers'
import Debug from 'debug'
import puppeteer from 'puppeteer'

import {
  getCachedBrowser,
  PUPPETEER_CACHE_DIR,
  refreshInstalledBrowserCache
} from './cache.js'
import { DEBUG_NAMESPACE } from './debug.config.js'

export const INSTALLER_TIMEOUT = 5 * 60 * 1000

const debug = Debug(`${DEBUG_NAMESPACE}:installers`)

/**
 * Installs the specified browser for Puppeteer.
 * Times out after 5 minutes.
 * @param browser - The browser to install ('chrome' or 'firefox').
 * @returns A promise that resolves when the installation is complete.
 */
export async function installBrowser(
  browser: 'chrome' | 'firefox'
): Promise<void> {
  const enumBrowser = browser === 'chrome' ? Browser.CHROME : Browser.FIREFOX
  const platform = detectBrowserPlatform() ?? BrowserPlatform.LINUX
  const buildId = await resolveBuildId(enumBrowser, platform, 'stable')

  try {
    await install({
      browser: enumBrowser,
      buildId,
      cacheDir: PUPPETEER_CACHE_DIR,
      unpack: true
    })

    await refreshInstalledBrowserCache()
  } catch (error) {
    debug('Error installing browser: %O', error)

    if (
      error instanceof Error &&
      error.message.includes('EEXIST: file already exists')
    ) {
      await refreshInstalledBrowserCache()
      return
    }

    throw error
  }
}

/**
 * Installs the Chrome browser for Puppeteer.
 */
export async function installChromeBrowser(): Promise<void> {
  await installBrowser('chrome')
}

/**
 * Installs the Firefox browser for Puppeteer.
 */
export async function installFirefoxBrowser(): Promise<void> {
  await installBrowser('firefox')
}

export interface TestInstalledBrowserResult {
  /** Whether the browser is installed and able to launch */
  success: boolean

  /** Whether the installer was run */
  ranInstaller: boolean
}

/**
 * Tests if the specified browser is installed.
 * @param browserName - The name of the browser to test ('chrome' or 'firefox').
 * @param installIfUnavailable - Whether to install the browser if it's not available.
 * @returns An object containing the installation status and whether the installer was run.
 */
export async function testInstalledBrowser(
  browserName: 'chrome' | 'firefox',
  installIfUnavailable = false
): Promise<TestInstalledBrowserResult> {
  let browser: puppeteer.Browser | undefined

  const installedBrowser = await getCachedBrowser(browserName)

  try {
    browser = await puppeteer.launch({
      browser: browserName,
      executablePath: installedBrowser?.executablePath,
      args: ['--no-sandbox']
    })

    return { ranInstaller: false, success: true }
  } catch (error) {
    if (installIfUnavailable) {
      await installBrowser(browserName)

      return {
        ...(await testInstalledBrowser(browserName, false)),
        ranInstaller: true
      } satisfies TestInstalledBrowserResult
    }

    debug('Browser not installed: %O', error)

    return { ranInstaller: false, success: false }
  } finally {
    await browser?.close()
  }
}

/**
 * Tests if the Puppeteer Chrome (or Chromium) browser is installed.
 * @param installIfUnavailable - Whether to install the browser if it's not available.
 * @returns A promise that resolves to an object containing the installation status and whether the installer was run.
 */
export async function testInstalledChromeBrowser(
  installIfUnavailable = false
): Promise<TestInstalledBrowserResult> {
  return await testInstalledBrowser('chrome', installIfUnavailable)
}

/**
 * Tests if the Puppeteer Firefox browser is installed.
 * @param installIfUnavailable - Whether to install the browser if it's not available.
 * @returns A promise that resolves to an object containing the installation status and whether the installer was run.
 */
export async function testInstalledFirefoxBrowser(
  installIfUnavailable = false
): Promise<TestInstalledBrowserResult> {
  return await testInstalledBrowser('firefox', installIfUnavailable)
}
