import { Injectable } from '@angular/core'
import { ExcelPasswordModalService } from '../shared/excel-password-modal/excel-password-modal.service'
import { EventService } from './event.service'
import { LicenceService } from './licence.service'
import { SpreadsheetUtil } from '../shared/spreadsheet-util/spreadsheet-util'
import { ParseParams } from '../models/ParseParams.interface'
import { ParseResult } from '../models/ParseResult.interface'
import { OpenOptions } from '../shared/excel-password-modal/models/options.interface'
import { Result } from '../shared/excel-password-modal/models/result.interface'
import * as XLSX from '@sheet/crypto'

@Injectable({
  providedIn: 'root'
})
export class SpreadsheetService {
  private licenceState = this.licenceService.licenceState

  constructor(
    private excelPasswordModalService: ExcelPasswordModalService,
    private eventService: EventService,
    private licenceService: LicenceService
  ) {}

  public parseExcelFile(
    parseParams: ParseParams,
    onParseStateChange?: (uploadState: string) => void,
    onTableFoundEvent?: (info: string) => void
  ): Promise<ParseResult | undefined> {
    const spreadSheetUtil = new SpreadsheetUtil({
      licenceState: this.licenceState
    })

    return spreadSheetUtil.parseSpreadsheetFile(
      parseParams,
      this.promptExcelPassword,
      onParseStateChange,
      onTableFoundEvent
    )
  }

  /**
   * Reads the excel file using the XLSX.read() function
   * If possible, function will use the web worker to read it in background thread
   * otherwise fallback method will be used
   *
   * @param file selected in an <input>
   * @returns WorkBook
   */
  public xlsxReadFile(file: any): Promise<XLSX.WorkBook> {
    return new Promise((resolve, reject) => {
      const spreadSheetUtil = new SpreadsheetUtil({
        licenceState: this.licenceState
      })

      let reader: FileReader = new FileReader()

      reader.onload = (fileReaderResponse: any) => {
        spreadSheetUtil
          .xslxStartReading(fileReaderResponse, this.promptExcelPassword)
          .then((response) => {
            resolve(response)
          })
          .catch((err) => {
            reject(err)
          })
      }

      reader.readAsArrayBuffer(file)
    })
  }

  /**
   * Read the file minimally just to get the sheet names, not reading full file
   * to help boost the performance
   *
   * @returns sheet names in string array
   */
  public async parseExcelSheetNames(file: File): Promise<{
    sheetNames: string[]
    password?: string
  }> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader()

      if (!file) {
        console.warn('file is missing')
        return resolve({ sheetNames: [] })
      }

      reader.onload = async (event: ProgressEvent<FileReader>) => {
        if (!event?.target) {
          console.warn('File reader event.target is missing')
          return
        }

        let wb: XLSX.WorkBook | undefined = undefined
        let fileUnlocking: boolean = false
        let password: string | undefined
        const data = event.target.result

        try {
          wb = XLSX.read(data, {
            // Load file minimally to parse sheets
            bookSheets: true,
            type: 'binary'
          })
        } catch (err: any) {
          if (err.message.toLowerCase().includes('password')) {
            fileUnlocking = true

            let passwordError = false

            while (fileUnlocking) {
              password = await this.promptExcelPassword({
                error: passwordError
              })

              if (password) {
                try {
                  wb = XLSX.read(data, {
                    // Load file minimally to parse sheets
                    bookSheets: true,
                    type: 'binary',
                    password: password
                  })

                  fileUnlocking = false
                  passwordError = false
                } catch (err: any) {
                  passwordError = true

                  if (!err.message.toLowerCase().includes('password')) {
                    fileUnlocking = false
                  }
                }

                if (!password)
                  return reject('Invalid password, failed to decrypt the file')
              } else {
                fileUnlocking = false
                return reject('No password provided')
              }
            }
          } else {
            return reject('Error reading the file')
          }
        }

        if (!wb) return reject('Error parsing the workbook')

        try {
          const sheetNames = wb.SheetNames

          return resolve({
            sheetNames: sheetNames,
            password: password
          })
        } catch (e) {
          console.error(e)
        }
      }

      reader.onerror = function (ex) {
        console.log(ex)
      }

      reader.readAsBinaryString(file)
    })
  }

  public bytesToMB(size: number): number {
    return parseFloat((size / (1024 * 1024)).toFixed(2))
  }

  /**
   * When excel is password protected we will display the password prompt for user to type password in.
   * @returns Password user input or undefined if discarded by user
   */
  private promptExcelPassword = (
    options?: OpenOptions
  ): Promise<string | undefined> => {
    return new Promise((resolve, reject) => {
      this.excelPasswordModalService
        .open(options)
        .subscribe((result: Result) => {
          resolve(result.password)
        })
    })
  }
}
