import '@things-factory/form-ui'
import '@things-factory/grist-ui'
import '@things-factory/import-ui'
import '@things-factory/barcode-ui'

import gql from 'graphql-tag'
import {
  css,
  html
} from 'lit-element'
import { connect } from 'pwa-helpers/connect-mixin'

import { DirectPrinter } from '@things-factory/barcode-base'
import { MultiColumnFormStyles } from '@things-factory/form-ui'
import {
  i18next,
  localize
} from '@things-factory/i18n-base'
import {
  client,
  CustomAlert,
  PageView,
  store
} from '@things-factory/shell'
import {
  CommonButtonStyles,
  ScrollbarStyles
} from '@things-factory/styles'
import { isMobileDevice } from '@things-factory/utils'

import { PRODUCT_LABEL_SETTING_KEY } from '../constants'

class PrintProductLabel extends connect(store)(localize(i18next)(PageView)) {
  static get styles() {
    return [
      MultiColumnFormStyles,
      ScrollbarStyles,
      css`
        :host {
          display: flex;
          flex-direction: column;
          overflow: hidden;
        }
        search-form {
          overflow: visible;
        }

        data-grist {
          overflow-y: auto;
          flex: 1;
        }
      `
    ]
  }

  static get properties() {
    return {
      config: Object,
      _searchingType: String
    }
  }

  render() {
    const SEARCH_TYPES = [
      { name: 'gan', value: 'GAN' },
      { name: 'ro', value: 'RO' },
      { name: 'inventory', value: 'INVENTORY' }
    ]

    return html`
      <form class="multi-column-form" id="info-form">
        <fieldset>
          <legend>${i18next.t('title.search_area')}</legend>
          <label>${i18next.t('label.search_type')}</label>
          <select
            name="orderType"
            @change="${e => {
              this._searchingType = e.currentTarget.value
            }}"
          >
            ${Object.keys(SEARCH_TYPES).map(
              key => html`
                <option value="${SEARCH_TYPES[key].value}">${i18next.t(`label.${SEARCH_TYPES[key].name}`)}</option>
              `
            )}
          </select>

          ${this._searchingType === SEARCH_TYPES.find(type => type.name === 'inventory').value
            ? html`
                <label>${i18next.t('label.product_info')}</label>
                <input name="productInfo" value="" type="text" @keypress="${this.orderNoKeypressHandler.bind(this)}" />

                <label>${i18next.t('label.batch_id')}</label>
                <input name="batchId" value="" type="text" @keypress="${this.orderNoKeypressHandler.bind(this)}" />

                <label>${i18next.t('label.lot_id')}</label>
                <input name="lotId" value="" type="text" @keypress="${this.orderNoKeypressHandler.bind(this)}" />
              `
            : html`
                <label>${i18next.t('label.order_no')}</label>
                <barcode-scanable-input
                  name="orderNo"
                  custom-input
                  @keypress="${this.orderNoKeypressHandler.bind(this)}"
                ></barcode-scanable-input>
              `}
        </fieldset>
      </form>

      <data-grist
        .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
        .config=${this.config}
        .fetchHandler="${this.fetchHandler.bind(this)}"
      ></data-grist>
    `
  }

  constructor() {
    super()
    this._searchingType = 'GAN'
  }

  get context() {
    return {
      title: i18next.t('title.print_product_label'),
      help: 'operato-wms/product-label/print-product-label',
      actions: [
        {
          title: i18next.t('button.print_label'),
          action: this._printProductLabel.bind(this),
          ...CommonButtonStyles.label
        }
      ]
    }
  }

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

  get _searchInfo() {
    if (this._searchingType == 'INVENTORY') {
      return {
        productInfo: this.shadowRoot.querySelector('input[name=productInfo]').value,
        batchId: this.shadowRoot.querySelector('input[name=batchId]').value,
        lotId: this.shadowRoot.querySelector('input[name=lotId]').value
      }
    } else
      return {
        orderNo: this.shadowRoot.querySelector('barcode-scanable-input[name=orderNo]').shadowRoot.querySelector('input')
          .value
      }
  }

  get _targetData() {
    return this.dataGrist.selected.map(record => {
      if (!record.printQty || record.printQty == '' || Number(record.printQty) < 1) {
        return {
          ...record,
          printQty: 1
        }
      } else {
        return record
      }
    })
  }

  pageInitialized() {
    this.config = {
      pagination: { infinite: true },
      rows: { appendable: false, selectable: { multiple: true } },
      list: {
        fields: [
          'palletId',
          'batchId',
          'gtin',
          'sku',
          'brandSku',
          'subBrand',
          'productName',
          'description',
          'auxUnit1',
          'auxValue1',
          'auxUnit2',
          'auxValue2',
          'expirationDate',
          'printQty'
        ]
      },
      columns: [
        { type: 'gutter', gutterName: 'sequence' },
        { type: 'gutter', gutterName: 'row-selector', multiple: true },
        {
          type: 'string',
          name: 'palletId',
          header: i18next.t('field.lot_id'),
          sortable: true,
          width: 150
        },
        {
          type: 'string',
          name: 'batchId',
          header: i18next.t('field.batch_id'),
          sortable: true,
          width: 150
        },
        {
          type: 'string',
          name: 'gtin',
          header: i18next.t('field.gtin'),
          sortable: true,
          width: 150
        },
        {
          type: 'string',
          name: 'brandSku',
          header: i18next.t('field.brand_sku_label'),
          sortable: true,
          width: 150
        },
        {
          type: 'string',
          name: 'subBrand',
          header: i18next.t('field.sub_brand'),
          sortable: true,
          width: 150
        },
        {
          type: 'string',
          name: 'sku',
          header: i18next.t('field.sku'),
          sortable: true,
          width: 150
        },
        {
          type: 'string',
          name: 'productName',
          header: i18next.t('field.product_name'),
          sortable: true,
          width: 200
        },
        {
          type: 'string',
          name: 'description',
          header: i18next.t('field.description'),
          sortable: true,
          width: 200
        },
        {
          type: 'string',
          name: 'type',
          header: i18next.t('field.product_type'),
          sortable: true,
          width: 120
        },
        {
          type: 'string',
          name: 'auxUnit1',
          header: i18next.t('field.aux_unit'),
          sortable: true,
          width: 80
        },
        {
          type: 'string',
          name: 'auxValue1',
          header: i18next.t('field.aux_value'),
          sortable: true,
          width: 80
        },
        {
          type: 'string',
          name: 'auxUnit2',
          header: i18next.t('field.aux_unit_2'),
          sortable: true,
          width: 100
        },
        {
          type: 'string',
          name: 'auxValue2',
          header: i18next.t('field.aux_value_2'),
          sortable: true,
          width: 100
        },
        {
          type: 'string',
          name: 'location',
          header: i18next.t('field.location'),
          sortable: true,
          width: 100
        },
        {
          type: 'string',
          name: 'expirationDate',
          header: i18next.t('field.exp_date'),
          sortable: true,
          record: { editable: true },
          width: 100
        },
        {
          type: 'number',
          name: 'printQty',
          header: i18next.t('field.print_qty'),
          record: { editable: true },
          width: 80
        }
      ]
    }
  }

  async fetchHandler({ page, limit, sorters = [] }) {
    const fetcherDetail = { page, limit, sorters }
    const searchType = this._searchingType
    const searchInfo = this._searchInfo

    if (!searchInfo || Object.values(searchInfo).every(search => search === ''))
      return {
        records: [],
        total: 0
      }

    switch (searchType) {
      case 'GAN':
        return await this._fetchArrivalNotice(searchInfo.orderNo)

      case 'RO':
        return await this._fetchReleaseOrder(searchInfo.orderNo)

      case 'INVENTORY':
        const filters = [{ name: 'status', operator: 'notin', value: ['INTRANSIT', 'TERMINATED', 'DELETED'] }]

        if (searchInfo.productInfo !== '')
          filters.push({ name: 'productInfo', operator: 'eq', value: searchInfo.productInfo })

        if (searchInfo.batchId !== '')
          filters.push({ name: 'batch_id', operator: 'i_like', value: `%${searchInfo.batchId}%` })

        if (searchInfo.lotId !== '')
          filters.push({ name: 'pallet_id', operator: 'i_like', value: `%${searchInfo.lotId}%` })

        return await this._fetchInventories(fetcherDetail, filters)
    }
  }

  async _fetchArrivalNotice(name) {
    try {
      const response = await client.query({
        query: gql`
          query arrivalNotice($name: String!) {
            arrivalNotice(name: $name) {
              orderInventories {
                inventory {
                  batchId
                  palletId
                  expirationDate
                  location {
                    name
                  }
                  product {
                    name
                    sku
                    brandSku
                    subBrand
                    description
                    type
                    auxUnit1
                    auxValue1
                    auxUnit2
                    auxValue2
                    productDetails {
                      name
                      gtin
                    }
                  }
                  productDetail {
                    productBarcodes {
                      gtin
                    }
                  }
                }
              }
              orderProducts {
                batchId
                palletQty
                actualPalletQty
                product {
                  name
                  sku
                  brandSku
                  subBrand
                  description
                  type
                  auxUnit1
                  auxValue1
                  auxUnit2
                  auxValue2
                  productDetails {
                    name
                    gtin
                  }
                }
                productDetail {
                  productBarcodes {
                    gtin
                  }
                }
              }
              bizplace {
                id
                name
              }
            }
          }
        `,
        variables: { name }
      })

      if (!response.errors) {
        const arrivalNotice = response.data.arrivalNotice
        const orderInventories = arrivalNotice.orderInventories
        const orderProducts = arrivalNotice.orderProducts

        if (orderInventories && orderInventories.length) {
          return {
            total: 0,
            records: orderInventories
              .map(oi => {
                const inventory = oi.inventory
                const product = inventory.product
                const productDetail = inventory.productDetail
                const record = {
                  batchId: inventory.batchId,
                  palletId: inventory.palletId,
                  location: inventory.location?.name || '',
                  expirationDate: inventory.expirationDate || '',
                  sku: product.sku,
                  brandSku: product.brandSku,
                  subBrand: product.subBrand,
                  description: product.description,
                  type: product.type,
                  auxUnit1: product.auxUnit1 || '',
                  auxValue1: product.auxValue1 || '',
                  auxUnit2: product.auxUnit2 || '',
                  auxValue2: product.auxValue2 || '',
                  bizplaceId: arrivalNotice.bizplace.id
                }

                if (productDetail.productBarcodes && productDetail.productBarcodes.length) {
                  return productDetail.productBarcodes.map(productBarcode => {
                    return {
                      ...record,
                      gtin: productBarcode.gtin,
                      productName: product.name
                    }
                  })
                } else {
                  return {
                    ...record,
                    gtin: '[N/A]',
                    productName: product.name
                  }
                }
              })
              .flat()
          }
        } else {
          return {
            total: 0,
            records: orderProducts
              .map(op => {
                const product = op.product
                const productDetail = op.productDetail
                const record = {
                  batchId: op.batchId,
                  palletId: '[N/A]',
                  location: '[N/A]',
                  expirationDate: '',
                  sku: product.sku,
                  brandSku: product.brandSku,
                  subBrand: product.subBrand,
                  description: product.description,
                  type: product.type,
                  printQty: op.actualPalletQty || op.palletQty || null,
                  auxUnit1: product.auxUnit1 || '',
                  auxValue1: product.auxValue1 || '',
                  auxUnit2: product.auxUnit2 || '',
                  auxValue2: product.auxValue2 || '',
                  bizplaceId: arrivalNotice.bizplace.id,
                  bizplaceName: arrivalNotice.bizplace.name
                }

                if (productDetail.productBarcodes && productDetail.productBarcodes.length) {
                  return productDetail.productBarcodes.map(productBarcode => {
                    return {
                      ...record,
                      gtin: productBarcode.gtin,
                      productName: product.name
                    }
                  })
                } else {
                  return {
                    ...record,
                    gtin: '[N/A]',
                    productName: product.name
                  }
                }
              })
              .flat()
          }
        }
      } else
        return {
          records: [],
          total: 0
        }
    } catch (e) {
      this._showToast(e)
    }
  }

  async _fetchReleaseOrder(name) {
    try {
      const response = await client.query({
        query: gql`
          query releaseGood($name: String!) {
            releaseGood(name: $name) {
              orderInventories {
                id
                batchId
                packingType
                releaseQty
                product {
                  name
                  sku
                  brandSku
                  subBrand
                  description
                  type
                  auxUnit1
                  auxValue1
                  auxUnit2
                  auxValue2
                  productDetails {
                    sku
                    name
                    description
                    gtin
                  }
                }
                inventory {
                  batchId
                  palletId
                  expirationDate
                  location {
                    name
                  }
                  product {
                    name
                    sku
                    brandSku
                    subBrand
                    description
                    type
                    auxUnit1
                    auxValue1
                    auxUnit2
                    auxValue2
                    productDetails {
                      name
                      gtin
                    }
                  }
                }
              }
              bizplace {
                id
                name
              }
            }
          }
        `,
        variables: { name }
      })

      if (!response.errors) {
        const releaseGood = response.data.releaseGood
        const orderInventories = releaseGood.orderInventories

        if (orderInventories && orderInventories.length) {
          // in case order inventory has inventory
          if (orderInventories.every(oi => oi.inventory)) {
            return {
              total: 0,
              records: orderInventories
                .map(oi => {
                  const inventory = oi.inventory
                  const product = inventory.product
                  const record = {
                    batchId: inventory.batchId,
                    palletId: inventory.palletId,
                    location: inventory.location?.name || '',
                    expirationDate: inventory.expirationDate || '',
                    sku: product.sku,
                    brandSku: product.brandSku,
                    subBrand: product.subBrand,
                    description: product.description,
                    type: product.type,
                    auxUnit1: product.auxUnit1 || '',
                    auxValue1: product.auxValue1 || '',
                    auxUnit2: product.auxUnit2 || '',
                    auxValue2: product.auxValue2 || '',
                    bizplaceId: releaseGood.bizplace.id,
                    bizplaceName: releaseGood.bizplace.name,
                    printQty: 1
                  }

                  if (product.productDetails && product.productDetails.length) {
                    return product.productDetails.map(productDetail => {
                      return {
                        ...record,
                        gtin: productDetail.gtin,
                        productName: product.name
                      }
                    })
                  } else {
                    return {
                      ...record,
                      gtin: '[N/A]',
                      productName: product.name
                    }
                  }
                })
                .flat()
            }
          } else {
            return {
              total: 0,
              records: orderInventories
                .map(oi => {
                  const product = oi.product
                  const record = {
                    batchId: oi.batchId,
                    palletId: '[N/A]',
                    location: '[N/A]',
                    expirationDate: '',
                    sku: product.sku,
                    brandSku: product.brandSku,
                    subBrand: product.subBrand,
                    description: product.description,
                    type: product.type,
                    auxUnit1: product.auxUnit1 || '',
                    auxValue1: product.auxValue1 || '',
                    auxUnit2: product.auxUnit2 || '',
                    auxValue2: product.auxValue2 || '',
                    bizplaceId: releaseGood.bizplace.id,
                    bizplaceName: releaseGood.bizplace.name
                  }

                  if (product.productDetails && product.productDetails.length) {
                    return product.productDetails.map(productDetail => {
                      return {
                        ...record,
                        gtin: productDetail.gtin,
                        productName: product.name
                      }
                    })
                  } else {
                    return {
                      ...record,
                      gtin: '[N/A]',
                      productName: product.name
                    }
                  }
                })
                .flat()
            }
          }
        }
      } else
        return {
          records: [],
          total: 0
        }
    } catch (e) {
      this._showToast(e)
    }
  }

  async _fetchInventories(fetcherDetail, filters) {
    const response = await client.query({
      query: gql`
        query inventories($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
          inventories(filters: $filters, pagination: $pagination, sortings: $sortings) {
            items {
              palletId
              batchId
              location {
                name
              }
              product {
                name
                sku
                brandSku
                subBrand
                description
                type
                auxUnit1
                auxValue1
                auxUnit2
                auxValue2
              }
              productDetail {
                name
                gtin
              }
              bizplace {
                id
                name
              }
              expirationDate
            }
            total
          }
        }
      `,
      variables: {
        filters,
        pagination: { page: 1, limit: 99999 },
        sortings: fetcherDetail.sorters
      }
    })

    if (!response.errors) {
      const inventories = response.data.inventories

      return {
        total: inventories.total,
        records: inventories.items
          .map(inventory => {
            const product = inventory.product
            const record = {
              batchId: inventory.batchId,
              palletId: inventory.palletId,
              location: inventory.location?.name || '',
              expirationDate: inventory.expirationDate || '',
              sku: product.sku,
              brandSku: product.brandSku,
              subBrand: product.subBrand,
              description: product.description,
              type: product.type,
              auxUnit1: product.auxUnit1 || '',
              auxValue1: product.auxValue1 || '',
              auxUnit2: product.auxUnit2 || '',
              auxValue2: product.auxValue2 || '',
              productInfo: product.description ? `${product.name} (${product.description})` : `${product.name}`,
              bizplaceId: inventory.bizplace.id,
              bizplaceName: inventory.bizplace.name
            }

            if (inventory?.productDetail) {
              return {
                ...record,
                gtin: inventory.productDetail.gtin,
                productName: inventory.productDetail.name
              }
            } else {
              return {
                ...record,
                gtin: '[N/A]',
                productName: product.name
              }
            }
          })
          .flat()
      }
    } else
      return {
        records: [],
        total: 0
      }
  }

  async _printProductLabel() {
    const labelId = this._productLabel && this._productLabel.id

    if (
      this.dataGrist.selected.some(record => !record.printQty || record.printQty == '' || Number(record.printQty) < 1)
    ) {
      const result = await CustomAlert({
        title: i18next.t('title.are_you_sure'),
        text: i18next.t('text.print_1_label_for_item_with_non_specified_print_qty'),
        confirmButton: { text: i18next.t('button.confirm') },
        cancelButton: { text: i18next.t('button.cancel') }
      })

      if (!result.value) return
    }

    const records = this._targetData

    if (!records?.length) {
      this._showToast({ message: `${i18next.t('text.please_select_the_product')}.` })
      return
    }

    if (!labelId) {
      this._showToast({
        level: 'error',
        message: `${i18next.t('text.no_label_setting_was_found')}. ${i18next.t('text.please_check_your_setting')}`
      })
    } else {
      for (var record of records) {
        let searchParams = new URLSearchParams()

        searchParams.append('gtin', record.gtin)
        searchParams.append('sku', record.sku)
        searchParams.append('brand_sku', record.brandSku)
        searchParams.append('sub_brand', record.subBrand)
        searchParams.append('product_name', record.productName)
        searchParams.append('description', record.description)
        searchParams.append('pallet_id', record.palletId)
        searchParams.append('batch_id', record.batchId)
        searchParams.append('aux_unit_1', record.auxUnit1)
        searchParams.append('aux_value_1', record.auxValue1)
        searchParams.append('aux_unit_2', record.auxUnit2)
        searchParams.append('aux_value_2', record.auxValue2)
        searchParams.append('expiration_date', record.expirationDate)
        searchParams.append('location', record.location)
        searchParams.append('company_name', record.bizplaceName)
        searchParams.append('type', record.type)

        let printCounter = 0
        try {
          for (let i = 0; i < record.printQty; i++) {
            const response = await fetch(`label-command/${labelId}?${searchParams.toString()}`, {
              method: 'GET'
            })

            if (response.status !== 200) {
              throw `Error : Can't get label command from server (response: ${response.status})`
            }

            var command = await response.text()

            if (!this.printer) {
              this.printer = new DirectPrinter()
            }

            await this.printer.print(command)
            printCounter += 1
          }
        } catch (ex) {
          this._showToast({ level: 'error', message: ex })
          delete this.printer
          break
        } finally {
          // we only need to update when the counter is more than 0
          if (printCounter > 0) {
            try {
              await this._updatePrintedLabelSequence(record.type, printCounter, record.bizplaceId)
            } catch (e) {
              this._showToast({
                level: 'error',
                message: i18next.t('text.unable_to_save_print_history_x', { state: { counter: printCounter } })
              })
              return
            }
          }
        }
      }
    }
  }

  async _updatePrintedLabelSequence(type, counter, bizplaceId) {
    try {
      await client.mutate({
        mutation: gql`
          mutation upsertSequence($type: String!, $counter: Float!, $bizplaceId: String!) {
            upsertSequence(type: $type, counter: $counter, bizplaceId: $bizplaceId) {
              pattern
              seq
            }
          }
        `,
        variables: {
          type,
          counter,
          bizplaceId
        }
      })
    } catch (error) {
      throw error
    }
  }

  stateChanged(state) {
    const productLabelSetting = state.dashboard[PRODUCT_LABEL_SETTING_KEY]
    this._productLabel = (productLabelSetting && productLabelSetting.board) || {}
  }

  orderNoKeypressHandler(e) {
    if (e.keyCode === 13) {
      e.preventDefault()

      this.dataGrist.fetch()
    }
  }

  _showToast({ level, type, message }) {
    document.dispatchEvent(
      new CustomEvent('notify', {
        detail: {
          type,
          level,
          message
        }
      })
    )
  }
}

window.customElements.define('print-product-label', PrintProductLabel)
