import {
  VuexModule,
  getModule,
  Action,
  Mutation,
  Module,
} from 'vuex-module-decorators'
import { Store } from 'vuex'
import ShipmentPoint from '../dto/ShipmentPoint'
import Country from '../dto/Country'
import Place from '../dto/Place'
import DeliveryCalculatorService from './DeliveryCalculatorService'
import CalculateRequestFactory from './CalculateRequestFactory'
import Currency from '../dto/Currency'
import CalculatedDelivery from '../dto/CalculatedDelivery'
import NotifyHelper from '../../../../helpers/NotifyHelper'
import CurrencyService from './CurrencyService'
import ShipmentPointsGroup from '../dto/ShipmentPointsGroup'
import ShipmentPointService from '../utils/ShipmentPointService'
import { Dimensions } from '../dto/Dimensions'

@Module({ name: 'DeliveryCalculatorStore', namespaced: true })
export default class DeliveryCalculatorStore extends VuexModule {
  shipmentPoints: ShipmentPoint[] = []
  shipmentPoint: ShipmentPoint | null = null
  country: Country | null = null
  place: Place | null = null
  dimensions: Dimensions = [0, 0, 0]
  weight: number | null = null
  price: number | null = null
  currency: Currency = CurrencyService.getDefaultCurrency()

  deliveries: CalculatedDelivery[] | null = null
  deliveryPoints: CalculatedDelivery[] | null = null

  errors: { [key: string]: string } = {}

  get isCalculated(): boolean {
    return this.deliveries !== null && this.deliveryPoints !== null
  }

  get isNotFound(): boolean {
    return (
      this.deliveries !== null &&
      this.deliveryPoints !== null &&
      this.deliveries.length === 0 &&
      this.deliveryPoints.length === 0
    )
  }

  get shipmentPointGroups(): ShipmentPointsGroup[] {
    const groupsByCountry: { [key: number]: ShipmentPoint[] } = {}
    const countries: { [key: number]: Country } = {}

    this.shipmentPoints.forEach(point => {
      if (!(point.country.id in groupsByCountry)) {
        groupsByCountry[point.country.id] = []
      }

      if (!(point.country.id in countries)) {
        countries[point.country.id] = point.country
      }

      groupsByCountry[point.country.id].push(point)
    })

    return Object.keys(groupsByCountry).map(key => {
      const countryId = parseInt(key)
      return {
        countryId: countryId,
        countryName: countries[countryId].name,
        points: groupsByCountry[countryId],
      }
    })
  }

  @Mutation
  setShipmentPoints(points: ShipmentPoint[]) {
    this.shipmentPoints = points
  }

  @Mutation
  setErrors(errors: { [key: string]: string }) {
    this.errors = errors
  }

  @Mutation
  setShipmentPoint(shipmentPoint: ShipmentPoint) {
    this.shipmentPoint = shipmentPoint
  }

  @Mutation
  setCountry(country: Country) {
    this.country = country
    this.place = null
  }

  @Mutation
  setPlace(place: Place) {
    this.place = place
  }

  @Mutation
  setWeight(value: number | null) {
    this.weight = value
  }

  @Mutation
  setPrice(value: number | null) {
    this.price = value
  }

  @Mutation
  setDimensions(dimensions: Dimensions) {
    this.dimensions = dimensions
  }

  @Mutation
  setCurrency(currency: Currency) {
    this.currency = currency
  }

  @Mutation
  setResult({
    deliveries,
    deliveryPoints,
  }: {
    deliveries: CalculatedDelivery[]
    deliveryPoints: CalculatedDelivery[]
  }) {
    this.deliveries = deliveries
    this.deliveryPoints = deliveryPoints
  }

  @Mutation
  clearResult() {
    this.deliveries = null
    this.deliveryPoints = null
  }

  @Action({ rawError: true })
  async loadShipmentPoints() {
    ShipmentPointService.getShipmentPoints().then(points =>
      this.setShipmentPoints(points)
    )
  }

  @Action({ rawError: true })
  async calculateDeliveries() {
    const destination = {
      country: this.country ? this.country.code : null,
      city: this.place ? this.place.code : null,
    }

    const shipmentPoint = this.shipmentPoint

    const source = {
      country: shipmentPoint ? shipmentPoint.country.code : null,
      state: shipmentPoint ? shipmentPoint.state.code : null,
      city: shipmentPoint ? shipmentPoint.city.code : null,
    }

    const request = CalculateRequestFactory({
      destination,
      source,
      weight: this.weight as number,
      dimensions: this.dimensions,
      price: this.price as number,
      currency: this.currency ? this.currency.code : null,
    })

    return Promise.all([
      DeliveryCalculatorService.calculateDelivery(request),
      DeliveryCalculatorService.calculateDeliveryPoints(request),
    ])
      .then(([deliveries, deliveryPoints]) => {
        this.setResult({ deliveries, deliveryPoints })
      })
      .catch(error => {
        if (!error.response.data.errors) {
          throw error
        }

        this.setErrors(error.response.data.errors)
      })
      .catch(() => NotifyHelper.serverError())
  }
}

export function getDeliveryCalculatorStore(store: Store<any>) {
  return getModule(DeliveryCalculatorStore, store)
}
