/*
 * <<
 * Davinci
 * ==
 * Copyright (C) 2016 - 2017 EDP
 * ==
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * >>
 */

import omit from 'lodash/omit'
import {
  getPreciseDefaultValue,
  getFilterParams,
  getVariableParams,
  getCustomOptionVariableParams
} from 'app/components/Control/util'
import { FieldSortTypes } from '../Widget/components/Config/Sort'
import { decodeMetricName } from '../Widget/components/util'
import {
  IDashboardItemInfo,
  IQueryConditions,
  IDataRequestParams,
  IDataRequestBody,
  IDashboardItem
} from './types'
import { IWidgetFormed } from '../Widget/types'
import ChartTypes from '../Widget/config/chart/ChartTypes'
import {
  IControl,
  IGlobalControlConditionsByItem,
  ILocalControlConditions,
  IGlobalControlConditions
} from 'app/components/Control/types'
import {
  ControlPanelTypes,
  ControlFieldTypes
} from 'app/components/Control/constants'
import {
  IFormedViews,
  IShareFormedViews,
  IViewQueryResponse
} from '../View/types'
import { IPaginationParams } from '../Widget/components/Widget'

export function getInitialPagination(widget: IWidgetFormed): IPaginationParams {
  const { mode, selectedChart, chartStyles } = widget.config
  if (mode === 'chart' && selectedChart === ChartTypes.Table) {
    const { withPaging, pageSize } = chartStyles.table
    return {
      withPaging,
      pageSize: withPaging ? Number(pageSize) : 0,
      pageNo: withPaging ? 1 : 0,
      totalCount: 0
    }
  } else {
    return null
  }
}

export function getInitialNativeQuery(widget: IWidgetFormed): boolean {
  const { mode, selectedChart, chartStyles } = widget.config
  if (mode === 'chart' && selectedChart === ChartTypes.Table) {
    return chartStyles.table.withNoAggregators
  } else {
    return false
  }
}

export function getInitialPaginationAndNativeQuery(widget: IWidgetFormed) {
  return {
    pagination: getInitialPagination(widget),
    nativeQuery: getInitialNativeQuery(widget)
  }
}

export function getUpdatedPagination(
  pagination: IPaginationParams,
  result: IViewQueryResponse
): IPaginationParams {
  if (!pagination) {
    return pagination
  }
  const { pageNo, pageSize, totalCount } = result
  return {
    ...pagination,
    pageNo: pagination.withPaging ? pageNo : 0,
    pageSize: pagination.withPaging ? pageSize : 0,
    totalCount
  }
}

export function getInitialItemInfo(
  widget: IWidgetFormed,
  formedViews: IFormedViews
): IDashboardItemInfo {
  return {
    datasource: {
      columns: [],
      pageNo: 0,
      pageSize: 0,
      totalCount: 0,
      resultList: []
    },
    loading: false,
    queryConditions: {
      linkageFilters: [],
      globalFilters: [],
      linkageVariables: [],
      globalVariables: [],
      drillpathInstance: [],
      ...getLocalControlInitialValues(widget.config.controls, formedViews),
      ...getInitialPaginationAndNativeQuery(widget)
    },
    shareToken: '',
    shareLoading: false,
    authorizedShareToken: '',
    downloadCsvLoading: false,
    interactId: '',
    rendered: false,
    renderType: 'rerender',
    selectedItems: [],
    errorMessage: ''
  }
}

interface IGlobalControlInitialValues {
  [itemId: string]: IGlobalControlConditions
}

export function getGlobalControlInitialValues(
  controls: IControl[],
  formedViews: IFormedViews | IShareFormedViews
): IGlobalControlInitialValues {
  const initialValues: IGlobalControlInitialValues = {}
  controls.forEach((control: IControl) => {
    const { optionWithVariable, relatedItems, relatedViews } = control
    const defaultValue = getPreciseDefaultValue(control)
    if (defaultValue) {
      Object.entries(relatedItems).forEach(([itemId, config]) => {
        Object.entries(relatedViews).forEach(([viewId, relatedView]) => {
          if (
            config.checked &&
            config.viewId === Number(viewId) &&
            formedViews[viewId]
          ) {
            const { model, variable } = formedViews[viewId]
            if (!initialValues[itemId]) {
              initialValues[itemId] = {
                globalFilters: [],
                globalVariables: []
              }
            }
            if (optionWithVariable) {
              const filterValue = getCustomOptionVariableParams(
                control,
                Number(viewId),
                defaultValue,
                variable
              )
              initialValues[itemId].globalVariables = initialValues[
                itemId
              ].globalVariables.concat(filterValue)
            } else {
              if (relatedView.fieldType === ControlFieldTypes.Column) {
                const filterValue = getFilterParams(
                  control,
                  relatedView.fields,
                  defaultValue,
                  model
                )
                initialValues[itemId].globalFilters = initialValues[
                  itemId
                ].globalFilters.concat(filterValue)
              } else {
                const filterValue = getVariableParams(
                  control,
                  relatedView.fields,
                  defaultValue,
                  variable
                )
                initialValues[itemId].globalVariables = initialValues[
                  itemId
                ].globalVariables.concat(filterValue)
              }
            }
          }
        })
      })
    }
  })
  return initialValues
}

export function getLocalControlInitialValues(
  controls: IControl[],
  formedViews: IFormedViews | IShareFormedViews
): ILocalControlConditions {
  const initialValues: ILocalControlConditions = {
    tempFilters: [], // @TODO combine widget static filters with local filters
    variables: []
  }
  controls.forEach((control: IControl) => {
    const { optionWithVariable, relatedViews } = control
    const defaultValue = getPreciseDefaultValue(control)
    if (defaultValue) {
      Object.entries(relatedViews).forEach(([viewId, relatedView]) => {
        if (formedViews[viewId]) {
          const { model, variable } = formedViews[viewId]
          if (optionWithVariable) {
            const filterValue = getCustomOptionVariableParams(
              control,
              Number(viewId),
              defaultValue,
              variable
            )
            initialValues.variables = initialValues.variables.concat(
              filterValue
            )
          } else {
            if (relatedView.fieldType === ControlFieldTypes.Column) {
              const filterValue = getFilterParams(
                control,
                relatedView.fields,
                defaultValue,
                model
              )
              initialValues.tempFilters = initialValues.tempFilters.concat(
                filterValue
              )
            } else {
              const filterValue = getVariableParams(
                control,
                relatedView.fields,
                defaultValue,
                variable
              )
              initialValues.variables = initialValues.variables.concat(
                filterValue
              )
            }
          }
        }
      })
    }
  })
  return initialValues
}

export function getRequestParams(
  widget: IWidgetFormed,
  cachedQueryConditions: IQueryConditions,
  flush: boolean,
  inputQueryConditions?: Partial<IQueryConditions>
): IDataRequestParams {
  const {
    cols,
    rows,
    metrics,
    secondaryMetrics,
    color,
    label,
    size,
    xAxis,
    tip,
    limit,
    cache,
    expired
  } = widget.config
  const customOrders = cols
    .concat(rows)
    .filter(({ sort }) => sort && sort.sortType === FieldSortTypes.Custom)
    .map(({ name, sort }) => ({
      name,
      list: sort[FieldSortTypes.Custom].sortList
    }))

  // @TODO combine widget static filters with local filters
  let tempFilters = cachedQueryConditions.tempFilters
  let linkageFilters = cachedQueryConditions.linkageFilters
  let globalFilters = cachedQueryConditions.globalFilters
  let paginationOrders = cachedQueryConditions.orders
  let variables = cachedQueryConditions.variables
  let linkageVariables = cachedQueryConditions.linkageVariables
  let globalVariables = cachedQueryConditions.globalVariables
  let pagination = cachedQueryConditions.pagination
  let nativeQuery = cachedQueryConditions.nativeQuery
  let drillStatus = cachedQueryConditions.drillStatus

  const prevDrillHistory = cachedQueryConditions.drillHistory
    ? cachedQueryConditions.drillHistory[
        cachedQueryConditions.drillHistory.length - 1
      ]
    : {}

  if (inputQueryConditions) {
    tempFilters = inputQueryConditions.tempFilters || tempFilters
    linkageFilters = inputQueryConditions.linkageFilters || linkageFilters
    globalFilters = inputQueryConditions.globalFilters || globalFilters
    paginationOrders = inputQueryConditions.orders || paginationOrders
    variables = inputQueryConditions.variables || variables
    linkageVariables = inputQueryConditions.linkageVariables || linkageVariables
    globalVariables = inputQueryConditions.globalVariables || globalVariables
    drillStatus = inputQueryConditions.drillStatus || prevDrillHistory
    pagination = inputQueryConditions.pagination || pagination
    nativeQuery =
      inputQueryConditions.nativeQuery !== void 0
        ? inputQueryConditions.nativeQuery
        : nativeQuery
  }

  let groups = cols
    .concat(rows)
    .filter((g) => g.name !== '指标名称')
    .map((g) => g.name)

  let aggregators = metrics.map((m) => ({
    column: decodeMetricName(m.name),
    func: m.agg
  }))

  let orders = widget.config.orders

  const filters = widget.config.filters.reduce(
    (a, b) => a.concat(b.config.sqlModel),
    []
  )

  if (secondaryMetrics && secondaryMetrics.length) {
    aggregators = aggregators.concat(
      secondaryMetrics.map((second) => ({
        column: decodeMetricName(second.name),
        func: second.agg
      }))
    )
  }

  if (color) {
    groups = groups.concat(color.items.map((c) => c.name))
  }
  if (label) {
    groups = groups.concat(
      label.items.filter((l) => l.type === 'category').map((l) => l.name)
    )
    aggregators = aggregators.concat(
      label.items
        .filter((l) => l.type === 'value')
        .map((l) => ({
          column: decodeMetricName(l.name),
          func: l.agg
        }))
    )
  }
  if (size) {
    aggregators = aggregators.concat(
      size.items.map((s) => ({
        column: decodeMetricName(s.name),
        func: s.agg
      }))
    )
  }
  if (xAxis) {
    aggregators = aggregators.concat(
      xAxis.items.map((x) => ({
        column: decodeMetricName(x.name),
        func: x.agg
      }))
    )
  }
  if (tip) {
    aggregators = aggregators.concat(
      tip.items.map((t) => ({
        column: decodeMetricName(t.name),
        func: t.agg
      }))
    )
  }
  if (paginationOrders) {
    orders = orders.concat(paginationOrders)
  }

  return {
    groups,
    aggregators,
    filters,
    tempFilters,
    linkageFilters,
    globalFilters,
    variables,
    linkageVariables,
    globalVariables,
    orders,
    limit,
    cache,
    expired,
    flush,
    pagination,
    nativeQuery,
    customOrders,
    drillStatus
  }
}

export function getRequestBody(
  requestParams: IDataRequestParams
): IDataRequestBody {
  const {
    filters,
    tempFilters, // @TODO combine widget static filters with local filters
    linkageFilters,
    globalFilters,
    variables,
    linkageVariables,
    globalVariables,
    pagination,
    drillStatus,
    groups,
    ...rest
  } = requestParams

  const { pageSize, pageNo } = pagination || { pageSize: 0, pageNo: 0 }

  let combinedFilters = filters
    .concat(tempFilters)
    .concat(linkageFilters)
    .concat(globalFilters)
  if (drillStatus && drillStatus.filters) {
    combinedFilters = combinedFilters.concat(drillStatus.filters)
  }

  return {
    ...omit(rest, 'customOrders'),
    groups: drillStatus && drillStatus.groups ? drillStatus.groups : groups,
    filters: combinedFilters,
    params: variables.concat(linkageVariables).concat(globalVariables),
    pageSize,
    pageNo
  }
}

export function getFormValuesRelatedItems(
  controls: IControl[],
  currentItems: IDashboardItem[],
  formValues: object
): number[] {
  return Object.keys(formValues).reduce((items, key) => {
    const control = controls.find((c) => c.key === key)
    const { relatedItems } = control
    const checkedItems = Object.entries(relatedItems)
      .filter(
        ([itemId, config]) =>
          config.checked && currentItems.find((c) => c.id === Number(itemId))
      )
      .map(([itemId]) => itemId)
    return Array.from(new Set([...items, ...checkedItems]))
  }, [])
}

export function getCurrentControlValues(
  type: ControlPanelTypes,
  controls: IControl[],
  formedViews: IFormedViews | IShareFormedViews,
  allFormValues: object,
  changedFormValues?: object,
  currentItems?: IDashboardItem[]
): IGlobalControlConditionsByItem | ILocalControlConditions {
  const updatedFormValues = {
    ...allFormValues,
    ...changedFormValues
  }

  if (type === ControlPanelTypes.Global) {
    const changedFormValuesRelatedItems = getFormValuesRelatedItems(
      controls,
      currentItems,
      changedFormValues || allFormValues
    )

    const conditionsByItem: IGlobalControlConditionsByItem = {}

    changedFormValuesRelatedItems.forEach((itemId) => {
      Object.entries(updatedFormValues).forEach(([key, value]) => {
        const control = controls.find((c) => c.key === key)

        if (control) {
          const { optionWithVariable, relatedViews, relatedItems } = control
          const relatedItem = relatedItems[itemId]

          if (
            relatedItem &&
            relatedItem.checked &&
            relatedViews[relatedItem.viewId] &&
            formedViews[relatedItem.viewId]
          ) {
            const relatedView = relatedViews[relatedItem.viewId]
            const { model, variable } = formedViews[relatedItem.viewId]
            if (!conditionsByItem[itemId]) {
              conditionsByItem[itemId] = {
                globalVariables: [],
                globalFilters: []
              }
            }
            if (optionWithVariable) {
              const controlVariables = getCustomOptionVariableParams(
                control,
                relatedItem.viewId,
                value,
                variable
              )
              conditionsByItem[itemId].globalVariables = conditionsByItem[
                itemId
              ].globalVariables.concat(controlVariables)
            } else {
              if (relatedView.fieldType === ControlFieldTypes.Column) {
                const controlFilters = getFilterParams(
                  control,
                  relatedView.fields,
                  value,
                  model
                )
                conditionsByItem[itemId].globalFilters = conditionsByItem[
                  itemId
                ].globalFilters.concat(controlFilters)
              } else {
                const controlVariables = getVariableParams(
                  control,
                  relatedView.fields,
                  value,
                  variable
                )
                conditionsByItem[itemId].globalVariables = conditionsByItem[
                  itemId
                ].globalVariables.concat(controlVariables)
              }
            }
          }
        }
      })
    })

    return conditionsByItem
  } else {
    const conditions: ILocalControlConditions = {
      tempFilters: [],
      variables: []
    }

    Object.entries(updatedFormValues).forEach(([key, value]) => {
      const control = controls.find((c) => c.key === key)

      if (control) {
        const [viewId, relatedView] = Object.entries(control.relatedViews)[0]
        if (formedViews[viewId]) {
          const { model, variable } = formedViews[viewId]
          if (control.optionWithVariable) {
            const controlVariables = getCustomOptionVariableParams(
              control,
              Number(viewId),
              value,
              variable
            )
            conditions.variables = conditions.variables.concat(controlVariables)
          } else {
            if (relatedView.fieldType === ControlFieldTypes.Column) {
              const controlFilters = getFilterParams(
                control,
                relatedView.fields,
                value,
                model
              )
              conditions.tempFilters = conditions.tempFilters.concat(
                controlFilters
              )
            } else {
              const controlVariables = getVariableParams(
                control,
                relatedView.fields,
                value,
                variable
              )
              conditions.variables = conditions.variables.concat(
                controlVariables
              )
            }
          }
        }
      }
    })

    return conditions
  }
}
