import fasterInventoryItemConstants from '@cityssm/faster-constants/inventory/items'
import {
  type FasterReportExporterOptions,
  FasterReportExporter
} from '@cityssm/faster-report-exporter'
import { csvReports, xlsxReports, xmlReports } from '@cityssm/faster-report-parser'
import { minutesToMillis } from '@cityssm/to-millis'
import Debug from 'debug'

import { DEBUG_NAMESPACE } from './debug.config.js'
import { delay, deleteFile } from './utilities.js'

const debug = Debug(`${DEBUG_NAMESPACE}:index`)

export type FasterUnofficialAPIOptions = Omit<
  FasterReportExporterOptions,
  'downloadFolderPath'
>

const timeoutMillis = minutesToMillis(1)

export class FasterUnofficialAPI {
  readonly #fasterReportExporter: FasterReportExporter

  /**
   * Initialize the Faster Unofficial API
   * @param fasterTenantOrBaseUrl - The subdomain of the FASTER Web URL before ".fasterwebcloud.com"
   *                                or the full domain and path including "/FASTER"
   * @param fasterUserName - The username to log in with
   * @param fasterPassword - The password to log in with
   * @param options - Options
   */
  constructor(
    fasterTenantOrBaseUrl: string,
    fasterUserName: string,
    fasterPassword: string,
    options: Partial<FasterUnofficialAPIOptions> = {}
  ) {
    this.#fasterReportExporter = new FasterReportExporter(
      fasterTenantOrBaseUrl,
      fasterUserName,
      fasterPassword,
      options
    )
  }

  /**
   * Retrieves a list of assets using the W114 report.
   * @returns A list of assets
   */
  async getAssets(): Promise<xlsxReports.W114AssetReportData[]> {
    debug('Exporting asset list...')

    const assetReportPath =
      await this.#fasterReportExporter.exportAssetList('Excel')

    debug(`Asset list exported: ${assetReportPath}`)

    debug('Parsing asset report...')

    const report = xlsxReports.parseW114ExcelReport(assetReportPath)

    debug(`Asset report parsed with ${report.data.length} asset(s).`)

    await deleteFile(assetReportPath)

    return report.data
  }

  /**
   * Retrieves a list of inventory items using the W200 report.
   * @returns A list of inventory items, grouped by storeroom
   */
  async getInventory(): Promise<xlsxReports.W200StoreroomReportData[]> {
    debug('Exporting inventory report...')

    const inventoryReportPath =
      await this.#fasterReportExporter.exportInventory('Excel')

    debug(`Inventory report exported: ${inventoryReportPath}`)

    debug('Parsing inventory report...')

    const report = xlsxReports.parseW200ExcelReport(inventoryReportPath)

    debug(`Inventory report parsed with ${report.data.length} storeroom(s).`)

    await deleteFile(inventoryReportPath)

    return report.data
  }

  /**
   * Retrieves a work order using the W399 report.
   * @param workOrderNumber - The work order number to retrieve.
   * @returns The work order details.
   */
  async getWorkOrder(workOrderNumber: number): Promise<xmlReports.W399TechnicianWorkOrderXmlResults> {

    debug(`Exporting work order ${workOrderNumber}...`)

    const workOrderPath =
      await this.#fasterReportExporter.exportWorkOrderTechnicianPrint(workOrderNumber, 'XML')

    debug(`Work order exported: ${workOrderPath}`)

    debug('Parsing work order report...')

    const report = await xmlReports.parseW399TechnicianWorkOrder(workOrderPath)

    debug(
      `Work order report parsed.`
    )

    await deleteFile(workOrderPath)

    return report
  }

  /**
   * Updates an inventory item, truncating fields where necessary.
   * @see {@link https://github.com/cityssm/node-faster-constants/blob/main/inventory/items.ts|@cityssm/faster-constants - Inventory Item Constants} for truncate lengths.
   * @param itemNumber - The item number of the inventory item to update.
   * @param storeroom - The storeroom of the inventory item to update.
   * @param fieldsToUpdate - The fields to update.
   * @param fieldsToUpdate.itemName - The updated item name.
   * @param fieldsToUpdate.itemDescription - The updated item description.
   * @param fieldsToUpdate.binLocation - The updated bin location.
   * @param fieldsToUpdate.alternateLocation - The updated alternate location.
   * @returns `true` if the inventory item was updated, `false` if not.
   */
  async updateInventoryItem(
    itemNumber: string,
    storeroom: string,
    fieldsToUpdate: {
      itemName?: string
      itemDescription?: string
      binLocation?: string
      alternateLocation?: string
    }
  ): Promise<boolean> {
    if (Object.keys(fieldsToUpdate).length === 0) {
      debug('No fields to update.')
      return false
    }

    if (
      (fieldsToUpdate.itemName ?? '').length >
      fasterInventoryItemConstants.itemName.maxLength
    ) {
      debug(
        `Item name exceeds maximum length of ${fasterInventoryItemConstants.itemName.maxLength} and will be truncated.`
      )
    }

    if (
      (fieldsToUpdate.binLocation ?? '').length >
      fasterInventoryItemConstants.binLocation.maxLength
    ) {
      debug(
        `Bin location exceeds maximum length of ${fasterInventoryItemConstants.binLocation.maxLength} and will be truncated.`
      )
    }

    if (
      (fieldsToUpdate.alternateLocation ?? '').length >
      fasterInventoryItemConstants.alternateLocation.maxLength
    ) {
      debug(
        `Alternate location exceeds maximum length of ${fasterInventoryItemConstants.alternateLocation.maxLength} and will be truncated.`
      )
    }

    const { browser, page } =
      await this.#fasterReportExporter._getLoggedInFasterPage()

    try {
      await page.goto(
        this.#fasterReportExporter.fasterUrlBuilder.inventorySearchUrl(
          itemNumber,
          true
        ),
        {
          timeout: timeoutMillis
        }
      )

      await page.waitForNetworkIdle({
        timeout: timeoutMillis
      })

      // No results or more than one result.
      // Not supported
      if (
        page
          .url()
          .startsWith(
            this.#fasterReportExporter.fasterUrlBuilder.inventorySearchUrl()
          )
      ) {
        debug('Either no results or multiple results found. Not supported.')
        return false
      }

      /*
       * Verify the storeroom
       */

      const headingText =
        (await page.$eval(
          // eslint-disable-next-line no-secrets/no-secrets
          '#RAD_SPLITTER_PANE_CONTENT_ctl00_ContentPlaceHolder_Content_panetop h1',
          (heading) => heading.textContent
        )) ?? ''

      if (!headingText.trim().endsWith(`[${storeroom}]`)) {
        debug('Item not found in the specified storeroom.')
        return false
      }

      /*
       * Get the item id
       */

      const pageUrlString = page.url()
      const pageUrl = new URL(pageUrlString)
      const itemId = pageUrl.searchParams.get('id') ?? ''

      if (itemId === '') {
        debug('Item ID not found.')
        return false
      }

      /*
       * Open the item identification update form
       */

      const formUrl = `${this.#fasterReportExporter.fasterUrlBuilder.baseUrl}/Domains/Parts/PartDetail/IdentificationEdit.aspx?id=${itemId}`

      await page.goto(formUrl, {
        timeout: timeoutMillis
      })

      await page.waitForNetworkIdle({
        timeout: timeoutMillis
      })

      /*
       * Update the fields
       */

      if (fieldsToUpdate.itemName !== undefined) {
        await page.$eval(
          `#${fasterInventoryItemConstants.itemName.inputId}`,
          (itemNameTextBox: HTMLInputElement, itemName) => {
            itemNameTextBox.value = itemName
          },
          fieldsToUpdate.itemName.slice(
            0,
            fasterInventoryItemConstants.itemName.maxLength
          )
        )
      }

      if (fieldsToUpdate.itemDescription !== undefined) {
        await page.$eval(
          '#PartDescriptionRadTextBox',
          (itemDescriptionTextBox: HTMLTextAreaElement, itemDescription) => {
            itemDescriptionTextBox.value = itemDescription
          },
          fieldsToUpdate.itemDescription
        )
      }

      if (fieldsToUpdate.binLocation !== undefined) {
        await page.$eval(
          `#${fasterInventoryItemConstants.binLocation.inputId}`,
          (binLocationTextBox: HTMLInputElement, binLocation) => {
            binLocationTextBox.value = binLocation
          },
          fieldsToUpdate.binLocation.slice(
            0,
            fasterInventoryItemConstants.binLocation.maxLength
          )
        )
      }

      if (fieldsToUpdate.alternateLocation !== undefined) {
        await page.$eval(
          `#${fasterInventoryItemConstants.alternateLocation.inputId}`,
          (alternateLocationTextBox: HTMLInputElement, alternateLocation) => {
            alternateLocationTextBox.value = alternateLocation
          },
          fieldsToUpdate.alternateLocation.slice(
            0,
            fasterInventoryItemConstants.alternateLocation.maxLength
          )
        )
      }

      /*
       * Save the form
       */

      await page.$eval('#SaveTopButton', (saveButton: HTMLButtonElement) => {
        saveButton.click()
      })

      await delay()

      await page.waitForNetworkIdle({
        timeout: timeoutMillis
      })

      return true
    } finally {
      try {
        await browser.close()
      } catch {
        // Ignore errors
      }
    }
  }

  /**
   * Retrieves the message log using the W603 report.
   * @param startDate - The start date of the message log to retrieve.
   * @param endDate - The end date of the message log to retrieve. Defaults to `startDate`.
   * @returns The message log.
   */
  async getMessageLog(
    startDate: Date,
    endDate?: Date
  ): Promise<csvReports.W603ReportRow[]> {
    debug('Exporting message log...')

    const messageLogPath = await this.#fasterReportExporter.exportMessageLogger(
      startDate,
      endDate ?? startDate,
      'CSV'
    )

    debug(`Message log exported: ${messageLogPath}`)

    debug('Parsing message log...')

    const report = await csvReports.parseFasterCsvReport(
      messageLogPath,
      csvReports.fasterCsvReportOptions.w603
    )

    debug(`Message log parsed with ${report.data.length} message(s).`)

    await deleteFile(messageLogPath)

    return report.data
  }

  /**
   * Executes an integration by name.
   * @param integrationName - The name of the integration to execute.
   * @returns `true` if the integration was executed, `false` if not.
   */
  async executeIntegration(integrationName: string): Promise<boolean> {
    const { browser, page } =
      await this.#fasterReportExporter._getLoggedInFasterPage()

    try {
      await page.goto(
        this.#fasterReportExporter.fasterUrlBuilder.integrationsUrl,
        {
          timeout: timeoutMillis
        }
      )

      await page.waitForNetworkIdle({
        timeout: timeoutMillis
      })

      // Find the integration row

      const integrationTableRowElements = await page.$$(
        // eslint-disable-next-line no-secrets/no-secrets
        '#ctl00_ContentPlaceHolder_Content_IntegrationRadDock_C_IntegrationRadGrid_ctl00 tbody tr'
      )

      for (const integrationTableRowElement of integrationTableRowElements) {
        const integrationNameElement =
          await integrationTableRowElement.$('td:nth-child(1) a')

        if (integrationNameElement === null) {
          continue
        }

        const integrationNameText = await integrationNameElement.evaluate(
          (cell) => cell.textContent
        )

        if (integrationNameText === integrationName) {
          debug(`Integration found: ${integrationName}`)

          const integrationActionLinkElements =
            await integrationTableRowElement.$$('td:nth-child(3) a')

          for (const integrationActionLinkElement of integrationActionLinkElements) {
            const integrationActionLinkText =
              await integrationActionLinkElement.evaluate(
                (cell) => cell.textContent
              )

            if (integrationActionLinkText === 'Execute') {
              debug(`Executing integration: ${integrationName}`)

              await integrationActionLinkElement.click()

              await delay()

              await page.waitForNetworkIdle({
                timeout: timeoutMillis
              })

              return true
            }
          }
        }
      }
    } finally {
      try {
        await browser.close()
      } catch {
        // Ignore errors
      }
    }

    return false
  }
}

export const integrationNames = {
  inventoryImportUtility: 'Inventory Import Utility'
}

export const parser = {
  csvReports,
  xlsxReports
}

export const exporter = {
  FasterReportExporter
}
