import React, { Component } from 'react'
import Hashids from 'hashids'

import ProductQuantity from './ProductQuantity'
import { t } from '$themelocalization'
import { isServer } from '$ustoreinternal/services/utils'
import PropertiesFormNG from '$core-components/PropertiesFormNG'
import { UStoreProvider } from '@ustore/core'
import { convertDate, isDate, mergeDeep, checkIfUploadedFileIsProof } from '../../services/utils'
import './ProductProperties.scss'
import {debounce} from "throttle-debounce";
import deepcopy from "deepcopy";


const withCustomProps = (WrappedComponent, additionalData = {}) => {
  class ConnectedComponent extends React.Component {
    constructor(props) {
      super(props);
      const regex = new RegExp(/[!$%^&*()+|~=`{}\[\]:";'<>?,.\/]/, 'g');
      this.id = this.props.id.replace('root_', '').replace(regex, '')

    }

    onDucChange = (value, errors = []) => {
      const newDucValue = value === 'default' ? undefined : value
      const schemaErrors = !value && this.props.uiSchema['ui:errorMessages']
        ? Object.values(this.props.uiSchema['ui:errorMessages']).map((err) => err)
        : []

      const errSchema = { [this.props.id]: { __errors: [...schemaErrors, ...errors] }}
      this.props.onChange(this.props.id, newDucValue, errSchema)
    }

    render() {
      const hashids = new Hashids(this.id)
      const hashedID = hashids.encode(1, 2, 3)

      // const propsWithoutOnChange = Object.keys(this.props)
      //   .filter((key) => key !== 'onChange')
      //   .reduce((acc, next) => ({...acc, [next]: this.props[next]}), {})

      return (
        <div className={`a${hashedID}`}>
          <WrappedComponent
            {...this.props}
            onChange={this.onDucChange}
            id={this.id}
            additionalData={additionalData}
            disabled={this.props.schema.disabled}
          />
        </div>
      )
    }
  }
  return ConnectedComponent
}

const widgets = !isServer() ? uStoreDucs.reduce((r, duc) => ({...r, [duc.name]: withCustomProps(duc.component)}), {})  : {}

class ProductPropertiesNG extends Component {
  constructor(props) {
    super(props)
    this.state = {
      ducErrors: [],
      formData: this.props.propertiesModel.formData,
      jsonSchema: this.props.propertiesModel.JSONSchema,
      uiSchema: this.props.propertiesModel.UISchema,
      allFormData: {}
    }

    this.iframeSrc = ''
  }

  updatePropertiesState = async (dataToUpdate) => {
    const { orderModel: { ID } } = this.props
    // send the data to BE to be saved in DB.
    const { currentProductProperties: productProperties } = UStoreProvider.state.customState.get()
   try {
     const res = await UStoreProvider.api.orders.updatePropertiesState(ID, dataToUpdate)

    if(Object.keys(res).includes('formData')) {
      let dataWithEnums = {}
    if(res.JSONSchema) {
      const newDefinitions = Object.keys(productProperties.JSONSchema.definitions).reduce((acc, key) => {
        let enums = {}
        let enumNames = {}

           if (res.JSONSchema.properties[key] && productProperties.JSONSchema.properties[key].enum) {
             enums = {enum: res.JSONSchema.properties[key].enum}
           }
           if (res.JSONSchema.properties[key] && productProperties.JSONSchema.definitions[key].enumNames) {
             enumNames = {enumNames: res.JSONSchema.properties[key].enumNames}
           }

           return {...acc, [key]: {...productProperties.JSONSchema.definitions[key], ...enums, ...enumNames}}
         }, {})

      dataWithEnums = {
        ...productProperties,
        UISchema: res.UISchema,
        formData: res.formData,
        JSONSchema: {
          ...productProperties.JSONSchema,
          properties: res.JSONSchema.properties,
          definitions: mergeDeep(res.JSONSchema.definitions, newDefinitions)
        }
      }
    } else {
      dataWithEnums = {
        ...productProperties,
        UISchema: res.UISchema
      }
    }
       return {
         ...productProperties,
         UISchema: dataWithEnums.UISchema,
         JSONSchema: {
           ...productProperties.JSONSchema,
           properties: dataWithEnums.JSONSchema.properties,
           definitions: dataWithEnums.JSONSchema.definitions,
           dependencies: res.JSONSchema.dependencies,
           required: res.JSONSchema.required
         },
         errors: res.errors ? res.errors : {},
         formData: Object.assign({}, productProperties.formData, res.formData),
       }
     } else {
       return {
         errorFromServer: res
       }
     }
   } catch (err) {
     return {
       errorFromServer: err
     }
   }
  }

  onPropertyChange = debounce(500, false, async (newData, hiddenErrors = [], visibleErrors = [], newSchema) => {
    const prevData = { ...this.state.formData }
    const { onFormChange, productModel } = this.props

    const providerState = UStoreProvider.state.customState.get()
    const productProperties = providerState.currentProductProperties

    let errors = [].concat(hiddenErrors || [])

    const changedProperty = Object.keys(newData)
      .find((propertyName) => newData[propertyName] !== undefined && newData[propertyName] !== prevData[propertyName])

    if (changedProperty) {
      // before sending the data to BE, check if ANY of the ducs is marked as 'autoSaveStateOnChange'
      // if at least one is true - send the entire data structure (not just the ones marked)
      let shouldUpdate = false
      const propertiesToUpdate = Object.keys(newData)
        .reduce((updatedProperties, key) => {
        const definition = productProperties.JSONSchema.definitions[key]
        const widget = productProperties.UISchema[key]['ui:widget']
        const toAPIValue = window.uStoreDucs.find(duc => duc.name === widget).component['toAPIValue']

        if (definition && definition.custom) {
          shouldUpdate = true
        }

        const isJsonString = (newData[key] ? newData[key].toString() : '').startsWith('___JSON___:')

        let newValue = !!newData[key] || newData[key] === 0
          ? isJsonString
            ? JSON.parse(newData[key].replace('___JSON___:',''))
            : newData[key]
          : ''

        if (toAPIValue && key !== changedProperty) {
          newValue = toAPIValue(prevData[key])
        }

        updatedProperties.push({
          id: definition.custom.id,
          value:  newValue
        })

        return updatedProperties
      }, [])

      if (shouldUpdate) {
        // save the new data to the state before calling the API so when it returns we can check
        // if the response is different from the request.
        const res = await this.updatePropertiesState(propertiesToUpdate)

        if(checkIfUploadedFileIsProof(res)){
          UStoreProvider.state.customState.set('currentProduct', {
            ...productModel,
            Proof: {
              ...productModel.Proof,
              MimeType: 'application/pdf'
            }
          })
        }

        if(!res.errorFromServer) {
          const errorsCopy = deepcopy(errors)

          const errorsFromRes = res && res.errors ? Object.keys(res.errors).reduce((acc, propertyName) => {
            errorsCopy.some((error) => {
              if(error.dataPath === `['${propertyName}']`) {
                if(error.keyword) {
                  acc = acc.concat({
                    message: res.UISchema[propertyName]['ui:errorMessages'][error.keyword],
                    property: propertyName,
                    dataPath: `[${propertyName}]`,
                    affectPrice: productProperties.JSONSchema.definitions[propertyName].custom.affectPrice
                  })
                }
              }
            })
            return acc
          }, []) : []

          errors = errors.concat(errorsFromRes)

          UStoreProvider.state.customState.set('currentProductProperties', {
            ...res,
            formData: res.formData,
            errors: errors
          })
          this.setState({
            jsonSchema: res.JSONSchema,
            uiSchema: res.UISchema,
            formData: res.formData,
            errors: res.errors ? res.errors : {},
            ducErrors: errors
          })

          onFormChange && onFormChange(newData, prevData, errors, visibleErrors, newSchema)
        } else {
          const propertiesToUpdateObject = propertiesToUpdate.reduce((acc, next) => ({...acc, [next.id]: next.value}), {})

          const updatedStateFormData = Object.keys(this.state.formData).reduce((acc, next) => {
            const key = next.split('_')[1]
            return {
              ...acc, [next]: propertiesToUpdateObject[key]
            }
          }, {})

          this.setState({
            formData: updatedStateFormData
          })
          onFormChange && onFormChange(res)
        }
      }
    }
  })

  render() {
    const {
      className,
      productModel,
      orderModel,
      onQuantityChange,
      isPriceCalculating,
      propertiesModel,
      validateNGDucs,
      onFirstValidate,
      onFirstAddToCart,
      translations,
    } = this.props
    const { formData, ducErrors, jsonSchema, uiSchema } = this.state

    if (!productModel || !propertiesModel || !propertiesModel.JSONSchema || !propertiesModel.UISchema) {
      return null
    }

    return (
      <div className={` ${className} product-properties`}>
        <div className='quantity'>
          <span className='quantity-label'>{t('product.quantity')}</span>
          <ProductQuantity
            supportsInventory={true}
            productModel={productModel}
            orderModel={orderModel}
            onQuantityChange={onQuantityChange}
          />
        </div>
        <PropertiesFormNG
          errors={ducErrors}
          widgets={widgets}
          orderId={orderModel.ID}
          jsonschema={jsonSchema}
          uischema={uiSchema}
          formData={formData}
          validateNGDucs={validateNGDucs}
          onFormDataChanged={this.onPropertyChange}
          isAddClicked={this.props.isAddClicked}
          onFirstValidate={onFirstValidate}
          onFirstAddToCart={onFirstAddToCart}
          translations={translations}
        />
      </div>
    )
  }
}

export default ProductPropertiesNG
