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

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

import { getCodeByName } from '@things-factory/code-base'
import { CONDITION_OF_GOODS } from '../constants'
import { i18next, localize } from '@things-factory/i18n-base'
import { client, PageView, store } from '@things-factory/shell'
import { ScrollbarStyles } from '@things-factory/styles'
import { flattenObject, isMobileDevice } from '@things-factory/utils'
import { applyParamsToFields } from '../../util'

class OnhandInventory extends connect(store)(localize(i18next)(PageView)) {
  static get styles() {
    return [
      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 {
      _email: String,
      _searchFields: Array,
      _expSearchFields: Array,
      _showExpiration: Boolean,
      config: Object,
      data: Object
    }
  }

  render() {
    this._showExpiration = this._showExpiration || false
    return html`
      <search-form id="search-form" .fields=${this._searchFields} @submit=${e => this.dataGrist.fetch()}></search-form>

      ${this._showExpiration
        ? html`<search-form
            id="search-exp-form"
            .fields=${this._expSearchFields}
            .hideSearchButton=${true}
            @submit=${e => this.dataGrist.fetch()}
          ></search-form>`
        : html``}

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

  get context() {
    return {
      title: i18next.t('title.onhand_inventory'),
      exportable: {
        name: i18next.t('title.onhand_inventory'),
        data: this._exportableData.bind(this)
      },
      help: 'operato-wms/inventory/onhand-inventory'
    }
  }

  async pageInitialized() {
    const partners = await this._fetchPartners()
    const _packingType = await getCodeByName('PACKING_TYPES')
    this.config = {
      list: {
        fields: [
          'palletId',
          'cartonId',
          'bizplace',
          'batchId',
          'initialInboundAt',
          'productSku',
          'productName',
          'productBrand',
          'packingType',
          'location',
          'remainQty',
          'expirationDate',
          'uomValue',
          'uom'
        ]
      },
      pagination: { pages: [20, 50, 100, 500, 999] },
      rows: {
        selectable: { multiple: true },
        appendable: false
      },
      columns: [
        { type: 'gutter', gutterName: 'sequence' },
        { type: 'gutter', gutterName: 'row-selector', multiple: true },
        {
          type: 'string',
          name: 'palletId',
          header: i18next.t('field.lot_id'),
          width: 150
        },
        {
          type: 'string',
          name: 'cartonId',
          header: i18next.t('field.carton_id'),
          width: 120
        },
        {
          type: 'string',
          name: 'batchId',
          header: i18next.t('field.batch_id'),
          imex: { header: i18next.t('field.batch_id'), key: 'batchId', width: 30, type: 'string' },
          width: 120
        },
        {
          type: 'string',
          name: 'initialInboundAt',
          header: i18next.t('field.initial_inbound_date'),
          width: 150
        },
        {
          type: 'string',
          name: 'bizplace_name',
          header: i18next.t('field.company'),
          sortable: true,
          width: 250
        },
        {
          type: 'string',
          name: 'product_sku',
          header: i18next.t('field.sku'),
          sortable: true,
          width: 150
        },
        {
          type: 'string',
          name: 'product_productName',
          header: i18next.t('field.product'),
          sortable: true,
          width: 500
        },
        {
          type: 'string',
          name: 'product_brand',
          header: i18next.t('field.brand'),
          sortable: true,
          width: 150
        },
        {
          type: 'code',
          name: 'packingType',
          header: i18next.t('field.packing_type'),
          record: { codeName: 'PACKING_TYPES' },
          width: 150
        },
        {
          type: 'number',
          name: 'remainQty',
          header: i18next.t('field.qty'),
          width: 80
        },
        {
          type: 'float',
          name: 'uomValue',
          header: i18next.t('field.total_uom_value'),
          width: 110
        },
        {
          type: 'float',
          name: 'transferQty',
          header: i18next.t('field.transfer_qty'),
          sortable: true,
          imex: {
            header: i18next.t('field.transfer_qty'),
            key: 'transferQty',
            width: 10,
            type: 'float'
          },
          width: 110
        },
        {
          type: 'float',
          name: 'transferUomValue',
          header: i18next.t('field.transfer_uom_value'),
          sortable: true,
          imex: {
            header: i18next.t('field.transfer_uom_value'),
            key: 'transferUomValue',
            width: 10,
            type: 'float'
          },
          width: 110
        },
        {
          type: 'number',
          name: 'nonLoadedQty',
          header: i18next.t('field.non_loaded_qty'),
          width: 80
        },
        {
          type: 'float',
          name: 'nonLoadedUomValue',
          header: i18next.t('field.non_loaded_uom_value'),
          width: 110
        },
        {
          type: 'string',
          name: 'uom',
          header: i18next.t('field.uom'),
          width: 80
        },
        {
          type: 'string',
          name: 'location_name',
          header: i18next.t('field.location'),
          sortable: true,
          width: 120
        },
        {
          type: 'date',
          name: 'expirationDate',
          header: i18next.t('field.exp_date'),
          width: 150
        },
        {
          type: 'date',
          name: 'manufactureDate',
          header: i18next.t('field.manufacture_date'),
          sortable: true,
          width: 120
        },
        {
          type: 'string',
          name: 'batchIdRef',
          header: i18next.t('field.batch_no_ref'),
          imex: { header: i18next.t('field.batch_no_ref'), key: 'batchIdRef', width: 30, type: 'string' },
          width: 120
        },
        {
          type: 'float',
          name: 'volume',
          header: i18next.t('field.volume_m3'),
          sortable: true,
          width: 110
        },
        {
          type: 'float',
          name: 'unitCost',
          header: i18next.t('field.unit_cost'),
          sortable: true,
          width: 80
        },
        {
          type: 'string',
          name: 'conditionOfGoods',
          header: i18next.t('field.inventory_quality'),
          width: 120,
          record: {
            renderer: (value, column, record, rowIndex, field) => {
              const condition = Object.values(CONDITION_OF_GOODS).find(c => c.value === value)
              return condition ? i18next.t(`label.${condition.name}`) : value || ''
            }
          }
        },
        {
          type: 'string',
          name: 'condition',
          header: i18next.t('field.condition'),
          width: 100
        },
        {
          type: 'string',
          name: 'expiry',
          header: i18next.t('field.expiry_status'),
          width: 120
        },
        {
          type: 'string',
          name: 'adjustmentNote',
          header: i18next.t('field.reason_of_defective'),
          width: 200
        },
        {
          type: 'string',
          name: 'remark',
          header: i18next.t('field.remark'),
          width: 200
        }
      ]
    }

    this._searchFields = [
      {
        label: i18next.t('field.company'),
        name: 'bizplace',
        type: 'select',
        options: [
          { value: '' },
          ...partners
            .map(partner => {
              return {
                name: `${partner.name} ${partner.description ? `(${partner.description})` : ''}`,
                value: partner.id
              }
            })
            .sort(this._compareValues('name', 'asc'))
        ],
        props: { searchOper: 'eq' }
      },
      {
        label: i18next.t('field.product_info'),
        name: 'product',
        type: 'text',
        props: { searchOper: 'eq' }
      },
      {
        label: i18next.t('field.batch_id'),
        name: 'batchId',
        type: 'text',
        props: { searchOper: 'i_like' }
      },
      {
        label: i18next.t('field.lot_id'),
        name: 'palletId',
        type: 'text',
        props: { searchOper: 'i_like' }
      },
      {
        label: i18next.t('field.carton_id'),
        name: 'cartonId',
        type: 'text',
        props: { searchOper: 'i_like' }
      },
      {
        label: i18next.t('field.packing_type'),
        name: 'packingType',
        type: 'select',
        options: [
          { value: '' },
          ..._packingType.map(packingType => {
            return {
              name: packingType.name,
              value: packingType.name
            }
          })
        ],
        props: { searchOper: 'eq' }
      },
      {
        label: i18next.t('field.location'),
        name: 'location',
        type: 'object',
        queryName: 'locations',
        field: 'name'
      },
      {
        label: i18next.t('field.date'),
        name: 'created_at',
        type: 'date',
        props: {
          searchOper: 'eq',
          max: new Date().toISOString().split('T')[0]
        },
        value: new Date().toISOString().split('T')[0]
      },
      {
        label: i18next.t('field.expiration'),
        name: 'expiration',
        type: 'checkbox',
        value: this._showExpiration,
        props: { searchOper: 'eq' },
        handlers: {
          change: () => {
            this._showExpiration = !this._showExpiration
          }
        }
      }
    ]

    this._expSearchFields = [
      {
        label: i18next.t('field.exp_from_date'),
        name: 'expFromDate',
        type: 'date',
        props: { searchOper: 'eq' },
        attrs: ['custom'],
        value: (() => {
          let date = new Date()
          date.setMonth(date.getMonth())
          return date.toISOString().split('T')[0]
        })(),
        handlers: { change: this._modifyDateRange.bind(this) }
      },
      {
        label: i18next.t('field.exp_to_date'),
        name: 'expToDate',
        type: 'date',
        props: { searchOper: 'eq' },
        attrs: ['custom'],
        value: (() => {
          let date = new Date()
          date.setMonth(date.getMonth() + 3)
          return date.toISOString().split('T')[0]
        })()
      }
    ]
  }

  pageUpdated(changes, lifecycle) {
    if (this.active) {
      let { params } = lifecycle
      if (params && this._searchFields) {
        if (params.expiration) {
          this._showExpiration = params.expiration
        }
        if (params.expRange) {
          switch (params.expRange) {
            case 'expired':
              params.expFromDate = new Date(new Date().setFullYear(new Date().getFullYear() - 1))
                .toISOString()
                .split('T')[0]
              params.expToDate = new Date().toISOString().split('T')[0]
              break
            case '6m':
              params.expFromDate = new Date().toISOString().split('T')[0]
              params.expToDate = new Date(new Date().setMonth(new Date().getMonth() + 6)).toISOString().split('T')[0]
              break
            case '9m':
              params.expFromDate = new Date().toISOString().split('T')[0]
              params.expToDate = new Date(new Date().setMonth(new Date().getMonth() + 9)).toISOString().split('T')[0]
              break
            default:
              break
          }
          const updatedExpFields = applyParamsToFields(this._expSearchFields, params)
          this._expSearchFields = updatedExpFields
          const expSearchForm = this.searchExpForm
          if (expSearchForm) {
            expSearchForm.fields = updatedExpFields
          }
        }

        const updatedFields = applyParamsToFields(this._searchFields, params)
        this._searchFields = updatedFields
        const searchForm = this.searchForm
        if (searchForm) {
          searchForm.fields = updatedFields
        }
      }
      this.dataGrist.fetch()
    }
  }

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

  get searchForm() {
    return this.shadowRoot.querySelector('#search-form')
  }

  get searchExpForm() {
    return this.shadowRoot.querySelector('#search-exp-form')
  }

  get _fromDateInput() {
    return this.searchForm.shadowRoot.querySelector('input[name=fromDate]')
  }

  get _toDateInput() {
    return this.searchForm.shadowRoot.querySelector('input[name=toDate]')
  }

  async fetchHandler({ page, limit, sorters = [] }) {
    const filters = await this.searchForm.getQueryFilters()
    const expFilters = (await this.searchExpForm?.getQueryFilters()) || []
    const response = await client.query({
      query: gql`
        query onhandInventories($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
          onhandInventories(filters: $filters, pagination: $pagination, sortings: $sortings) {
            items {
              palletId
              cartonId
              batchId
              batchIdRef
              packingType
              uom
              uomValue
              expiry
              bizplace {
                id
                name
                description
              }
              product {
                id
                sku
                name
                brand
                description
                volume
                outboundAlert
                minOutboundShelfLife
              }
              qty
              remainQty
              nonLoadedQty
              nonLoadedUomValue
              transferQty
              transferUomValue
              warehouse {
                id
                name
                description
              }
              location {
                id
                name
                description
                type
              }
              updatedAt
              updater {
                name
                description
              }
              initialInboundAt
              expirationDate
              manufactureDate
              remark
              unitCost
              conditionOfGoods
              adjustmentNote
              condition
            }
            total
          }
        }
      `,
      variables: {
        filters: [
          ...filters,
          ...expFilters,
          { name: 'status', operator: 'notin', value: ['INTRANSIT', 'TERMINATED', 'DELETED'] },
          { name: 'remainOnly', operator: 'eq', value: true },
          { name: 'unlockOnly', operator: 'eq', value: true },
          { name: 'timezoneOffset', operator: 'eq', value: new Date().getTimezoneOffset() }
        ],
        pagination: { page, limit },
        sortings: sorters
      }
    })

    // Logging response for debugging
    console.log(response)

    return {
      total: response.data.onhandInventories.total || 0,
      records:
        response.data.onhandInventories.items.map(item => {
          return flattenObject(
            {
              ...item,
              expirationDate: item.expirationDate ? new Date(item.expirationDate).toDateString().slice(4, 16) : '',
              product: {
                ...item.product,
                productName: `${item.product.name} ${item.product.description}`,
                productBrand: `${item.product.brand}`
              },
              volume: parseFloat(item.product.volume * item.remainQty).toFixed(4)
              // Need to move to serverside processing and fetch with db query for consistent performance
            },
            '_'
          )
        }) || []
    }
  }

  // Need to move to serverside processing and fetch with db query for consistent performance
  async _fetchPartners() {
    const filters = []
    const response = await client.query({
      query: gql`
        query partnersBizplaces($filters: [Filter!]) {
          partnersBizplaces(filters: $filters) {
            items {
              id
              name
              description
            }
          }
        }
      `,
      variables: { filters }
    })

    if (!response.errors) {
      return response.data.partnersBizplaces.items
    }
  }

  get _columns() {
    return this.config.columns
  }

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

      var headerSetting = [
        { header: i18next.t('field.lot_id'), key: 'palletId', width: 25, type: 'string' },
        { header: i18next.t('field.carton_id'), key: 'cartonId', width: 25, type: 'string' },
        { header: i18next.t('field.batch_id'), key: 'batchId', width: 25, type: 'string' },
        { header: i18next.t('field.initial_inbound_date'), key: 'initialInboundAt', width: 25, type: 'datetime' },
        { header: i18next.t('field.company'), key: 'bizplace_name', width: 30, type: 'string' },
        { header: i18next.t('field.sku_code'), key: 'product_sku', width: 25, type: 'string' },
        { header: i18next.t('field.product'), key: 'product_name', width: 40, type: 'string' },
        { header: i18next.t('field.brand'), key: 'product_brand', width: 25, type: 'string' },
        { header: i18next.t('field.product_description'), key: 'product_description', width: 50, type: 'string' },
        { header: i18next.t('field.packing_type'), key: 'packingType', width: 20, type: 'string' },
        { header: i18next.t('field.qty'), key: 'remainQty', width: 15, type: 'string' },
        { header: i18next.t('field.total_uom_value'), key: 'uomValue', width: 25, type: 'string' },
        { header: i18next.t('field.non_loaded_qty'), key: 'nonLoadedQty', width: 25, type: 'string' },
        { header: i18next.t('field.non_loaded_uom_value'), key: 'nonLoadedUomValue', width: 25, type: 'string' },
        { header: i18next.t('field.uom'), key: 'uom', width: 15, type: 'string' },
        { header: i18next.t('field.location'), key: 'location_name', width: 25, type: 'string' },
        { header: i18next.t('field.exp_date'), key: 'expirationDate', width: 30, type: 'string' },
        { header: i18next.t('field.manufacture_date'), key: 'manufactureDate', width: 40, type: 'string' },
        { header: i18next.t('field.batch_no_ref'), key: 'batchIdRef', width: 50, type: 'string' },
        { header: i18next.t('field.volume_m3'), key: 'volume', width: 25, type: 'float' },
        { header: i18next.t('field.unit_cost'), key: 'unitCost', width: 25, type: 'float' },
        { header: i18next.t('field.condition'), key: 'condition', width: 25, type: 'string' },
        { header: i18next.t('field.reason_of_defective'), key: 'adjustmentNote', width: 25, type: 'string' },
        { header: i18next.t('field.expiry_status'), key: 'expiry', width: 25, type: 'string' },
        { header: i18next.t('field.remark'), key: 'remark', width: 40, type: 'string' }
      ]

      if (this.dataGrist.selected && this.dataGrist.selected.length > 0) {
        data = this.dataGrist.selected.map(item => {
          const formatter = new Intl.DateTimeFormat(navigator.language)
          const expirationDate = item.expirationDate ? formatter.format(new Date(item.expirationDate)) : ''
          const manufactureDate = item.manufactureDate ? formatter.format(new Date(item.manufactureDate)) : ''

          return {
            ...item,
            expirationDate: expirationDate,
            manufactureDate: manufactureDate
          }
        })
      } else {
        const bizplaceFilters = (await this.searchForm.getQueryFilters()).filter(x => x.name === 'bizplace')
        if (bizplaceFilters.length == 0) {
          throw new Error(`Please select a customer for export.`)
        }
        data = (await this.fetchHandler({ page: 1, limit: 9999999 })).records.map(item => {
          const formatter = new Intl.DateTimeFormat(navigator.language)
          const expirationDate = item.expirationDate ? formatter.format(new Date(item.expirationDate)) : ''
          const manufactureDate = item.manufactureDate ? formatter.format(new Date(item.manufactureDate)) : ''

          return {
            ...item,
            expirationDate: expirationDate,
            manufactureDate: manufactureDate
          }
        })
      }

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

  _compareValues(key, order = 'asc') {
    return function innerSort(a, b) {
      if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
        return 0
      }

      const varA = typeof a[key] === 'string' ? a[key].toUpperCase() : a[key]
      const varB = typeof b[key] === 'string' ? b[key].toUpperCase() : b[key]

      let comparison = 0
      if (varA > varB) {
        comparison = 1
      } else if (varA < varB) {
        comparison = -1
      }
      return order === 'desc' ? comparison * -1 : comparison
    }
  }

  _modifyDateRange(e) {
    const fromDate = e.currentTarget.value

    if (this._toDateInput.value < fromDate) this._toDateInput.value = fromDate

    let min = new Date(fromDate)
    let today = new Date()
    today.setHours(0, 0, 0, 0)

    min = min.toISOString().split('T')[0]

    this._toDateInput.min = min
  }

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

window.customElements.define('onhand-inventory', OnhandInventory)
