import '@things-factory/grist-ui'
import '@things-factory/barcode-ui'
import './generate-manifest-popup'
import './select-manifest-popup'
import '@things-factory/import-ui/client/components/import-pop-up'

import gql from 'graphql-tag'
import { css, html } from 'lit-element'

import { MultiColumnFormStyles } from '@things-factory/form-ui'
import { i18next, localize } from '@things-factory/i18n-base'
import { openPopup } from '@things-factory/layout-base'
import { client, CustomAlert, navigate, PageView } from '@things-factory/shell'
import { CommonButtonStyles } from '@things-factory/styles'
import { isMobileDevice, sleep } from '@things-factory/utils'

import { ORDER_STATUS } from '../constants'

class CreateLoadingManifest extends localize(i18next)(PageView) {
  static get styles() {
    return [
      MultiColumnFormStyles,
      css`
        :host {
          display: flex;
          flex-direction: column;
          overflow-x: auto;
        }

        .container {
          flex: 1;
          display: flex;
          overflow-y: auto;
          min-height: 50vh;
        }
        .grist {
          background-color: var(--main-section-background-color);
          display: flex;
          flex-direction: column;
          flex: 1;
          overflow-y: auto;
        }
        data-grist {
          overflow-y: hidden;
          flex: 1;
        }
        h2 {
          padding: var(--subtitle-padding);
          font: var(--subtitle-font);
          color: var(--subtitle-text-color);
          border-bottom: var(--subtitle-border-bottom);
        }
        .grist h2 {
          margin: var(--grist-title-margin);
          border: var(--grist-title-border);
          font: var(--grist-title-font);
          color: var(--secondary-color);
        }

        .grist h2 mwc-icon {
          vertical-align: middle;
          margin: var(--grist-title-icon-margin);
          font-size: var(--grist-title-icon-size);
          color: var(--grist-title-icon-color);
        }
        .box {
          display: flex;
          flex-direction: column;
          color: rgb(58, 88, 119);
          margin-left: 30px;
        }

        .box h4 {
          margin-top: 5px;
          margin-bottom: 5px;
        }

        h2 + data-grist {
          padding-top: var(--grist-title-with-grid-padding);
        }
      `
    ]
  }

  static get properties() {
    return {
      shippingData: Object,
      shippingGristConfig: Object,
      name: String,
      shippingProvider: String,
      id: String,
      audioCtx: Object,
      selectedManifest: Object,
      _removePackages: Array,
      scannedNo: Number
    }
  }

  get context() {
    return {
      title: i18next.t('title.manifest'),
      actions: [
        {
          title: i18next.t('button.existing_manifest'),
          action: async () => {
            this._clearManifestInfo()
            await sleep(1010)
            navigate('existing_manifest_list')
          },
          ...CommonButtonStyles.submit
        },
        {
          title: i18next.t('button.create_new_manifest'),
          action: this._popupGenerateManifest.bind(this),
          ...CommonButtonStyles.submit
        }
      ],
      importable: {
        handler: this.importableData.bind(this)
      },
      exportable: {
        name: i18next.t('title.manifest'),
        data: this.exportableData.bind(this)
      }
    }
  }

  render() {
    return html`
      <form name="manifest" class="multi-column-form" autocomplete="off">
        <fieldset>
          <legend>${i18next.t('title.create_loading_manifest')}</legend>
          <label>${i18next.t('label.scan_tracking_no')}</label>
          <barcode-scanable-input
            name="trackingNo"
            custom-input
            @keypress="${e => {
              if (e.keyCode === 13) {
                if (!this.selectedManifest.name) {
                  this._showToast({ message: i18next.t('text.please_select_an_existing_manifest_or_create_a_new_one') })
                  return
                }
                e.preventDefault()
                if (this.trackingNoInput.value) {
                  this._fetchManifestItems(this.trackingNoInput.value)
                }
              }
            }}"
          ></barcode-scanable-input>
        </fieldset>
      </form>

      <div class="box">
        <h4>${i18next.t('label.manifest_no')} : ${this.selectedManifest?.name}</h4>
        <h4>${i18next.t('label.courier')} : ${this.selectedManifest?.shippingProvider}</h4>
        <h4>${i18next.t('label.scanned')} : ${this.scannedNo}</h4>
      </div>

      <div class="container">
        <div class="grist">
          <h2><mwc-icon>list_alt</mwc-icon>${i18next.t('title.manifest_items')}</h2>

          <data-grist
            .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
            .config=${this.shippingGristConfig}
            .data=${this.shippingData}
          ></data-grist>
        </div>
      </div>
    `
  }

  constructor() {
    super()
    this.shippingData = { records: [] }
    this.audioCtx = new (window.AudioContext || window.webkitAudioContext)()
    this._removePackages = []
    this.scannedNo = 0
    this.shippingGristConfig = {}
  }

  get shippingGrist() {
    return this.shadowRoot.querySelector('data-grist')
  }

  get manifestForm() {
    return this.shadowRoot.querySelector('form[name=manifest]')
  }

  get trackingNoInput() {
    return this.shadowRoot.querySelector('barcode-scanable-input[name=trackingNo]')?.shadowRoot.querySelector('input')
  }

  get columns() {
    return [...this.shippingGristConfig.columns, ...this.importGristConfig.columns]
  }

  pageUpdated(changes, lifecycle, before) {
    if (lifecycle?.params) {
      const manifestInfo = {
        name: lifecycle.params.name,
        shippingProvider: lifecycle.params.shippingProvider,
        id: lifecycle.params.id
      }

      this.selectedManifest = manifestInfo
      this._clearManifestInfo()
    }

    if (this.active) {
      this._focusOnTrackingNoInput()
    }
  }

  pageInitialized() {
    this._clearManifestInfo()

    this.shippingGristConfig = {
      list: { fields: ['refNo', 'trackingNo', 'transporter'] },
      pagination: { infinite: true },
      columns: [
        { type: 'gutter', gutterName: 'sequence' },
        {
          type: 'gutter',
          gutterName: 'button',
          icon: 'close',
          handlers: {
            click: async (columns, data, column, record, rowIndex) => {
              if (this.shippingData.records.length > 0 && record.name) {
                const newData = data.records.filter((_, idx) => idx !== rowIndex)
                this._removePackages.push(record.name)
                await this._updateManifestDetails()
                this.shippingData = { ...this.shippingData, records: newData }
                this.scannedNo -= 1
                this.shippingGrist.dirtyData.records = newData
              }
            }
          }
        },
        {
          type: 'string',
          name: 'refNo',
          label: true,
          header: i18next.t('field.order_no'),
          width: 200
        },
        {
          type: 'string',
          name: 'trackingNo',
          label: true,
          header: i18next.t('field.tracking_no'),
          imex: { header: i18next.t('field.tracking_no'), key: 'trackingNo', width: 30, type: 'string' },
          width: 200
        },
        {
          type: 'string',
          name: 'transporter',
          label: true,
          header: i18next.t('field.transporter'),
          imex: { header: i18next.t('field.transporter'), key: 'transporter', width: 30, type: 'string' },
          width: 200
        },
        {
          type: 'string',
          name: 'bizplaceName',
          label: true,
          header: i18next.t('field.company'),
          width: 200
        },
        {
          type: 'string',
          name: 'status',
          label: true,
          header: i18next.t('field.status'),
          width: 200
        }
      ]
    }

    this.importGristConfig = {
      list: { fields: ['refNo', 'trackingNo', 'transporter'] },
      pagination: { infinite: true },
      columns: [
        { type: 'gutter', gutterName: 'sequence' },
        {
          type: 'string',
          name: 'trackingNo',
          label: true,
          header: i18next.t('field.tracking_no'),
          imex: { header: i18next.t('field.tracking_no'), key: 'trackingNo', width: 30, type: 'string' },
          width: 200
        },
        {
          type: 'string',
          name: 'transporter',
          label: true,
          header: i18next.t('field.transporter'),
          imex: { header: i18next.t('field.transporter'), key: 'transporter', width: 30, type: 'string' },
          width: 200
        },
        {
          type: 'string',
          name: 'remark',
          label: true,
          header: i18next.t('field.remark'),
          imex: { header: i18next.t('field.remark'), key: 'remarks', width: 100, type: 'string' },
          width: 300
        }
      ]
    }
  }

  async _fetchManifestItems() {
    try {
      this._validateTrackingNo()

      const response = await client.query({
        query: gql`
          query orderPackage($trackingNo: String) {
            orderPackage(trackingNo: $trackingNo) {
              id
              name
              trackingNo
              transporter
              releaseGood {
                id
                name
                refNo
                status
              }
              bizplace {
                id
                name
              }
              status
              manifestedAt
            }
          }
        `,
        variables: { trackingNo: this.trackingNoInput.value.trim() }
      })
      if (!response.errors) {
        const orderPackage = response.data.orderPackage
        const manifestTransport = this.selectedManifest.shippingProvider

        if (orderPackage) {
          let manifestItems = this.shippingData.records || []

          // edit sini
          if (manifestItems.some(itm => itm.trackingNo == orderPackage.trackingNo)) {
            this._playSound('error')
            let result = await CustomAlert({
              type: 'warning',
              title: i18next.t('title.failed'),
              text: [
                i18next.t('text.tracking_no_duplicated') +
                  '\n' +
                  i18next.t('text.please_take_necessary_action_to_prevent_any_potential_issues')
              ],
              confirmButton: { text: i18next.t('button.back') }
            })
            if (result.value) return
          }

          let orderPackageTransporter =
            orderPackage.transporter?.toLowerCase() == 'ems' ? 'efmx' : orderPackage.transporter?.toLowerCase()
          if (manifestTransport?.toLowerCase() !== orderPackageTransporter) {
            this._popupSelectManifest(orderPackage)
            return
          }

          if (
            orderPackage?.releaseGood?.status === ORDER_STATUS.CANCELLED.value ||
            orderPackage?.releaseGood?.status === ORDER_STATUS.PENDING_CANCEL.value
          ) {
            this._playSound('error')

            let answer = await CustomAlert({
              type: 'warning',
              title: i18next.t('title.order(s)_is_at_pending_cancelled_or_cancelled'),
              text: i18next.t('text.do_you_want_to_ignore'),
              confirmButton: { text: i18next.t('button.ignore') },
              cancelButton: { text: i18next.t('button.cancel') }
            })

            if (!answer?.value) {
              this._clearTrackingNoInput()
              return
            }
          }

          if (orderPackage.manifestedAt != null) {
            this._playSound('error')
            this._showToast({
              message: i18next.t('text.tracking_no_already_exist_in_other_manifest_list'),
              duration: 3000
            })
            return
          }

          const trackingNoAdded = await this._addTrackingNo()

          manifestItems.push({
            ...orderPackage,
            bizplaceName: orderPackage.bizplace.name,
            refNo: orderPackage.releaseGood.refNo,
            orderNo: orderPackage.releaseGood.refNo,
            transporter: orderPackage.transporter == 'EMS' ? 'EFMX' : orderPackage.transporter
          })
          this.shippingData = { records: manifestItems }

          if (trackingNoAdded) {
            this.scannedNo += 1
          }

          this._playSound()
        } else {
          this._playSound('error')
          this._showToast({ message: i18next.t('text.tracking_no_already_exist_in_manifest_list') })
        }

        this._clearTrackingNoInput()
        this.pageUpdated()
      } else {
        this._playSound('error')
      }
    } catch (e) {
      this._playSound('error')
      this._showToast(e)
    }
  }

  _focusOnTrackingNoInput() {
    setTimeout(() => this.trackingNoInput.focus(), 50)
  }

  async _addTrackingNo() {
    const response = await client.mutate({
      mutation: gql`
        mutation addTrackingNo($manifestId: String, $trackingNo: String, $shippingProvider: String) {
          addTrackingNo(manifestId: $manifestId, trackingNo: $trackingNo, shippingProvider: $shippingProvider)
        }
      `,
      variables: {
        manifestId: this.selectedManifest.id,
        trackingNo: this.trackingNoInput.value.trim(),
        shippingProvider: this.selectedManifest.shippingProvider
      }
    })

    if (!response.errors) {
      this.trackingNoInput.value = ''
      this._focusOnTrackingNoInput()
      return true
    } else {
      let errorMessage = response.errors[0].message

      if (errorMessage.includes('amp;')) {
        response.errors[0].message = errorMessage.replace('amp;', '')
        this._showToast({ message: response.errors[0].message })
      }
      if (errorMessage.includes('manifest has been dispatch')) this._playSound('error')
      throw new Error(i18next.t('error.this_manifest_has_been_dispatch'))
    }
  }

  _popupSelectManifest(orderPackage) {
    openPopup(
      html`
        <select-manifest-popup
          .shippingCourier="${orderPackage.transporter}"
          @manifestSelected="${e => {
            this._popupGenerateManifest(true)
          }}"
          style="height:200px;"
        >
        </select-manifest-popup>
      `,
      {
        backdrop: true,
        size: 'small',
        title: i18next.t('title.new_courier')
      }
    )

    this.pageUpdated()
  }

  async _popupGenerateManifest(popupSelect) {
    let selectManifest = popupSelect
    await sleep(1010)
    openPopup(
      html`
        <generate-manifest-popup
          .isManifestNew="${selectManifest}"
          @manifestGenerated="${async e => {
            this.selectedManifest = e.detail.generatedManifest
            this.pageUpdated()
          }}"
          style="height:120px;"
        >
        </generate-manifest-popup>
      `,
      {
        backdrop: true,
        size: 'small',
        title: i18next.t('title.create_new_manifest')
      }
    )

    this._clearPage()
  }

  async exportableData() {
    try {
      let data = []

      var headerSetting = [
        ...this.shippingGristConfig.columns
          .filter(column => column.type !== 'gutter' && column.name !== undefined && column.imex !== undefined)
          .map(column => {
            return column.imex
          })
      ]

      return { header: headerSetting, data: data }
    } catch (e) {
      this.showToast(e)
    }
  }

  _focusOnTrackingNoInput() {
    setTimeout(() => this.trackingNoInput.focus(), 50)
  }

  _getManifestItems() {
    return this.shippingGrist.data.records.map(record => {
      return {
        refNo: record.refNo,
        trackingNo: record.trackingNo,
        transporter: record.transporter
      }
    })
  }

  async _updateManifestDetails() {
    if (this._removePackages?.length == 0) {
      return
    }

    try {
      const response = await client.mutate({
        mutation: gql`
          mutation updateManifest(
            $manifestId: String
            $status: String
            $shippingProvider: String
            $isPreventDeleteManifest: Boolean
            $removePackages: [String!]
          ) {
            updateManifest(
              manifestId: $manifestId
              status: $status
              shippingProvider: $shippingProvider
              isPreventDeleteManifest: $isPreventDeleteManifest
              removePackages: $removePackages
            )
          }
        `,
        variables: {
          manifestId: this.selectedManifest.id,
          status: this._selectedStatus,
          isPreventDeleteManifest: true,
          removePackages: this._removePackages
        }
      })

      let errorMessage = response?.errors?.[0]?.message ? response.errors[0].message : ''
      if (!response.errors) {
        this._showToast({ message: i18next.t('text.tracking_no_deleted') })
      }

      if (errorMessage?.includes('manifest has been dispatch')) {
        this._playSound('error')
        throw new Error(i18next.t('error.this_manifest_has_been_dispatch'))
      }
    } catch (e) {
      this._showToast(e)
      throw e
    }
  }

  importableData(records) {
    if (!this.selectedManifest.name) {
      this._showToast({ message: i18next.t('text.please_select_an_existing_manifest_or_create_a_new_one') })
      return
    }

    let rows = {
      selectable: { multiple: true }
    }

    setTimeout(() => {
      openPopup(
        html`
          <import-pop-up
            .records=${records}
            .config=${{
              rows,
              columns: [...this.importGristConfig.columns.filter(column => column.imex !== undefined)]
            }}
            .importHandler="${this.importHandler.bind(this)}"
          ></import-pop-up>
        `,
        {
          backdrop: true,
          size: 'large',
          title: i18next.t('title.import_manifest')
        }
      )
    }, 500)
  }

  async importHandler(records) {
    try {
      const newRecord = await this._validateProducts(records)
      let recordsWithRemarks

      if (newRecord) {
        recordsWithRemarks = newRecord
      } else {
        recordsWithRemarks = records
      }

      const hasRemarks = recordsWithRemarks.some(itm => itm.remark)

      if (!hasRemarks) {
        const response = await client.mutate({
          mutation: gql`
            mutation submitTrackingNos($patches: [ManifestItem!]!, $manifestId: String) {
              submitTrackingNos(patches: $patches, manifestId: $manifestId) {
                id
                name
                trackingNo
                transporter
                releaseGood {
                  id
                  name
                  refNo
                  status
                }
                bizplace {
                  id
                  name
                }
                status
                manifestedAt
              }
            }
          `,
          variables: { patches: records, manifestId: this.selectedManifest.id }
        })

        if (!response.errors) {
          let updatedRecords = response.data.submitTrackingNos.map(itm => {
            return {
              ...itm,
              bizplaceName: itm.bizplace.name,
              refNo: itm.releaseGood.refNo,
              transporter: itm.transporter == 'EMS' ? 'EFMX' : itm.transporter
            }
          })

          this.shippingData = {
            records: [...this.shippingData.records, ...updatedRecords]
          }

          this.scannedNo = this.shippingData.records.length
        } else {
          throw new Error(i18next.t('error.this_manifest_has_been_dispatch'))
        }
      }

      history.back()

      if (hasRemarks) {
        this.importableData(recordsWithRemarks)
      }
    } catch (e) {
      this._showToast(e)
      history.back()
      throw e
    }
  }

  async _validateProducts(records = []) {
    if (!records?.length) throw new Error(i18next.t('text.no_tracking_no'))

    records.forEach(item => {
      if (!item.trackingNo || !item.transporter) {
        item.remark = 'Missing information'
      }
    })

    const duplicatedTrackingNos = records.filter((item, index, array) => {
      return array.findIndex(otherItem => otherItem.trackingNo === item.trackingNo) !== index
    })

    if (duplicatedTrackingNos?.length > 0) {
      duplicatedTrackingNos.forEach(item => {
        const index = records.findIndex(record => record.trackingNo === item.trackingNo)
        if (index !== -1) {
          records[index].remark = 'Tracking No duplicated'
        }
      })
    }

    const recordsWithoutRemark = records.filter(item => !item.remark)
    const recordsWithRemark = records.filter(item => item.remark)
    let response

    if (recordsWithoutRemark.length > 0) {
      response = await client.query({
        query: gql`
          query validateTrackingNo($patches: [ManifestItem!]!) {
            validateTrackingNo(patches: $patches) {
              cuFlag
              trackingNo
              transporter
              remark
            }
          }
        `,
        variables: { patches: recordsWithoutRemark }
      })

      const existingOrderPackages = response.data.validateTrackingNo

      const gristRecords = this.shippingGrist.data.records

      existingOrderPackages.forEach(record => {
        const matchingOrderPackage = existingOrderPackages.find(
          orderPackage => orderPackage.trackingNo === record.trackingNo
        )

        if (gristRecords.some(gristRecord => gristRecord.trackingNo === record.trackingNo)) {
          this._showToast({
            message:
              i18next.t('text.tracking_no_duplicated') +
              '\n' +
              i18next.t('text.please_take_necessary_action_to_prevent_any_potential_issues'),
            duration: 3000
          })
          record.remark = 'Tracking No duplicated'
        } else if (matchingOrderPackage?.manifest) {
          record.remark = 'Tracking No exist in another manifest'
        } else if (record.transporter.toLowerCase() !== this.selectedManifest.shippingProvider.toLowerCase()) {
          record.remark = 'Tracking No not allowed in this manifest'
        }
      })

      existingOrderPackages.push(...recordsWithRemark)

      if (existingOrderPackages.some(item => item.remark)) {
        return existingOrderPackages
      }
    }
  }

  _validateTrackingNo() {
    if (!this.trackingNoInput?.value) {
      this._focusOnTrackingNoInput()
      throw new Error(i18next.t('text.tracking_no_is_empty'))
    }
  }

  _clearTrackingNoInput() {
    this.trackingNoInput.value = ''
    this._focusOnTrackingNoInput()
  }

  _clearPage() {
    this._clearTrackingNoInput()
    this.shippingData = { ...this.shippingData, records: [] }
    this.scannedNo = 0
  }

  _clearManifestInfo() {
    this.name = ''
    this.shippingProvider = ''
    this.id = ''
    this.shippingData = { ...this.shippingData, records: [] }
    this.scannedNo = 0
  }

  _showToast({ type, message, duration }) {
    document.dispatchEvent(
      new CustomEvent('notify', {
        detail: {
          type,
          message,
          option: { timer: duration ? duration : 1500 }
        }
      })
    )
  }

  _playSound(type) {
    let oscillator = this.audioCtx.createOscillator()
    let gainNode = this.audioCtx.createGain()
    let timing = 100

    oscillator.connect(gainNode)
    gainNode.connect(this.audioCtx.destination)

    switch (type) {
      case 'error':
        gainNode.gain.value = 0.5
        oscillator.frequency.value = 150
        oscillator.type = 'square'
        timing = 250
        break

      default:
        gainNode.gain.value = 0.8
        oscillator.frequency.value = 850
        oscillator.type = 'triangle'
        break
    }

    oscillator.start()

    setTimeout(function () {
      oscillator.stop()
    }, timing)
  }
}

customElements.define('create-loading-manifest', CreateLoadingManifest)
