import { getPaperSize } from '@cityssm/paper-sizes'
import launchPuppeteer, { type puppeteer } from '@cityssm/puppeteer-launch'
import Debug from 'debug'
import exitHook from 'exit-hook'

import { DEBUG_NAMESPACE } from './debug.config.js'
import {
  type PDFPuppeteerOptions,
  defaultPdfOptions,
  defaultPdfPuppeteerOptions,
  defaultPuppeteerOptions,
  htmlNavigationTimeoutMillis,
  urlNavigationTimeoutMillis
} from './defaultOptions.js'

const debug = Debug(`${DEBUG_NAMESPACE}:index`)

let cachedBrowser: puppeteer.Browser | undefined

/**
 * Converts HTML or a webpage into HTML using Puppeteer.
 * @param html - An HTML string, or a URL.
 * @param instancePdfOptions - PDF options for Puppeteer.
 * @param instancePdfPuppeteerOptions - pdf-puppeteer options.
 * @returns A Buffer of PDF data.
 */
// eslint-disable-next-line complexity
export async function convertHTMLToPDF(
  html: string,
  instancePdfOptions: puppeteer.PDFOptions = {},
  instancePdfPuppeteerOptions: Partial<PDFPuppeteerOptions> = {}
): Promise<Uint8Array> {
  if (typeof html !== 'string') {
    throw new TypeError(
      'Invalid Argument: HTML expected as type of string and received a value of a different type. Check your request body and request headers.'
    )
  }

  const pdfPuppeteerOptions = {
    ...defaultPdfPuppeteerOptions,
    ...instancePdfPuppeteerOptions
  }

  /*
   * Initialize browser
   */

  const puppeteerOptions = { ...defaultPuppeteerOptions }

  puppeteerOptions.browser = pdfPuppeteerOptions.browser ?? 'chrome'

  if (pdfPuppeteerOptions.disableSandbox) {
    puppeteerOptions.args = ['--no-sandbox', '--disable-setuid-sandbox']
  }

  let browser: puppeteer.Browser | undefined
  let doCloseBrowser = false
  let isRunningPdfGeneration = false

  try {
    let puppeteerLaunchFunction = launchPuppeteer

    if (pdfPuppeteerOptions.usePackagePuppeteer) {
      const puppeteerPackage = await import('puppeteer')
      puppeteerLaunchFunction = puppeteerPackage.launch
    }

    if (pdfPuppeteerOptions.cacheBrowser) {
      cachedBrowser ??= await puppeteerLaunchFunction(puppeteerOptions)

      browser = cachedBrowser
    } else {
      doCloseBrowser = true
      browser = await puppeteerLaunchFunction(puppeteerOptions)
    }

    const browserVersion = await browser.version()

    debug(`Browser: ${browserVersion}`)

    const browserIsFirefox = browserVersion.toLowerCase().includes('firefox')

    /*
     * Initialize page
     */

    const page = await browser.newPage()

    const remoteContent = pdfPuppeteerOptions.remoteContent

    if (pdfPuppeteerOptions.htmlIsUrl) {
      debug('Loading URL...')
      await page.goto(html, {
        waitUntil: browserIsFirefox ? 'domcontentloaded' : 'networkidle0',
        timeout: urlNavigationTimeoutMillis
      })
    } else if (remoteContent) {
      debug('Loading HTML with remote content...')
      await page.goto(
        `data:text/html;base64,${Buffer.from(html).toString('base64')}`,
        {
          waitUntil: browserIsFirefox ? 'domcontentloaded' : 'networkidle0',
          timeout: urlNavigationTimeoutMillis
        }
      )
    } else {
      debug('Loading HTML...')
      await page.setContent(html, {
        timeout: htmlNavigationTimeoutMillis
      })
    }

    debug('Content loaded.')

    const pdfOptions = { ...defaultPdfOptions, ...instancePdfOptions }

    // Fix "format" issue
    if (pdfOptions.format !== undefined) {
      const size = getPaperSize(pdfOptions.format)
      // eslint-disable-next-line sonarjs/different-types-comparison, @typescript-eslint/no-unnecessary-condition
      if (size !== undefined) {
        delete pdfOptions.format
        pdfOptions.width = `${size.width}${size.unit}`
        pdfOptions.height = `${size.height}${size.unit}`
      }
    }

    debug('Converting to PDF...')

    // eslint-disable-next-line sonarjs/no-dead-store
    isRunningPdfGeneration = true

    const pdfBuffer = await page.pdf(pdfOptions)

    // eslint-disable-next-line sonarjs/no-dead-store
    isRunningPdfGeneration = false

    debug('PDF conversion done.')

    await page.close()

    if (!pdfPuppeteerOptions.cacheBrowser || cachedBrowser !== browser) {
      await browser.close()
    }

    return pdfBuffer
  } catch (error) {
    if (
      isRunningPdfGeneration &&
      defaultPuppeteerOptions.browser === 'chrome'
    ) {
      if (!doCloseBrowser) {
        await closeCachedBrowser()
      }

      defaultPuppeteerOptions.browser = 'firefox'

      debug('Trying again with Firefox.')

      return await convertHTMLToPDF(
        html,
        instancePdfOptions,
        instancePdfPuppeteerOptions
      )
    } else {
      // eslint-disable-next-line @typescript-eslint/only-throw-error
      throw error
    }
  } finally {
    try {
      if (doCloseBrowser && browser !== undefined) {
        debug('Closing browser...')
        await browser.close()
        debug('Browser closed.')
      }
    } catch {
      // ignore
    }
  }
}

export default convertHTMLToPDF

/**
 * Closes the cached browser instance.
 */
export async function closeCachedBrowser(): Promise<void> {
  if (cachedBrowser !== undefined) {
    try {
      await cachedBrowser.close()
    } catch {
      // ignore
    }
    cachedBrowser = undefined
  }
}

/**
 * Checks for any cached browser instance.
 * @returns True is a cached browser instance exists.
 */
export function hasCachedBrowser(): boolean {
  return cachedBrowser !== undefined
}

export {
  type PDFPuppeteerOptions,
  defaultPdfOptions,
  defaultPdfPuppeteerOptions
} from './defaultOptions.js'

exitHook(() => {
  debug('Running exit hook.')
  void closeCachedBrowser()
})
