import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import {useSelector} from '../index'
import { Dispatch, useCallback } from 'react'
import { DpsResult, Result } from './Result'
import { DevInfo, DpValue } from 'tuya-panel-kit'
import { NativeApi } from '../../api/native'
import { useDispatch } from 'react-redux'
import { GlobalParams } from '../GlobalParams'
import { isNumber, snakeCase } from 'lodash'
import { PropertyValueTypes } from '../../utils/TypeUtils'
import { DeviceInfo as GroupDeviceInfo } from '../../api/native'

export interface NativeProps {
  familyName: string
  role: 0 | 1 | 2 | 3
  deviceInfo: DeviceInfo
  uaGroupInfo: UAGroupInfo
  timeSchedule?: boolean
  energieverbrauch?: object
  moods: any[]
  flags: any[]
  flagModeState: FlagModeState
  is24HourClock: boolean
  timeZone: string
}

interface FlagModeState {
  flagMode: boolean
  flagId?: number
}

export interface DeviceInfo {
  devId: string
  pId: string
  dps: any
}

export interface UAGroupInfo {
  tyGroupId: number
  pId: string
  dps: any
  config: any
  groupDevices: GroupDeviceInfo[]
  name: string
  deviceIds: string[]
  icon?: string
}

const initialState: NativeProps = {
  familyName: '',
  role: 2,
  deviceInfo: {
    devId: '',
    pId: '',
    dps: {},
  },
  uaGroupInfo: {
    tyGroupId: -1,
    pId: '',
    dps: {},
    config: {},
    groupDevices: [],
    name: '',
    icon: undefined,
    deviceIds: []
  },
  timeSchedule: false,
  energieverbrauch: {},
  moods: [],
  flags: [],
  flagModeState:{
    flagMode: false,
    flagId: undefined
  },
  is24HourClock: true,
  timeZone: 'Europe/Berlin'
}

// energy generation
const productList = [
  'rcqe1i17x0vrvws7',
]

const fanProductList = [
  'urcqn70htlshvigb',
]

const nativePropsSlice = createSlice({
  name: 'ldvModules',
  initialState: initialState,
  reducers: {
    setNativeProps: (state, action: PayloadAction<NativeProps>) => {
      state.deviceInfo.devId = action.payload.deviceInfo.devId
      if (!!action.payload.familyName) {
        state.familyName = action.payload.familyName
      }
      if (isNumber(action.payload.role)) {
        state.role = action.payload.role
      }
      if (!!action.payload.deviceInfo.pId) {
        state.deviceInfo.pId = action.payload.deviceInfo.pId
      }
      state.deviceInfo.dps = { ...state.deviceInfo.dps, ...action.payload.deviceInfo.dps }
    },
    setGroupNativeProps: (state, action: PayloadAction<NativeProps>) => {
      state.uaGroupInfo.tyGroupId = action.payload.uaGroupInfo.tyGroupId
      if (!!action.payload.uaGroupInfo.name) {
        state.uaGroupInfo.name = action.payload.uaGroupInfo.name
      }
      if (!!action.payload.familyName) {
        state.familyName = action.payload.familyName
      }
      if (!!action.payload.uaGroupInfo.name) {
        state.uaGroupInfo.name = action.payload.uaGroupInfo.name
      }
      if (isNumber(action.payload.role)) {
        state.role = action.payload.role
      }
      if (!!action.payload.uaGroupInfo.pId) {
        state.uaGroupInfo.pId = action.payload.uaGroupInfo.pId
      }
      if (!!action.payload.uaGroupInfo.icon) {
        state.uaGroupInfo.icon = action.payload.uaGroupInfo.icon
      }
      state.uaGroupInfo.dps = { ...state.uaGroupInfo.dps, ...action.payload.uaGroupInfo.dps }
      state.uaGroupInfo.config = action.payload.uaGroupInfo.config
      state.uaGroupInfo.deviceIds = action.payload.uaGroupInfo.deviceIds ?? []
    },
    setGroupConfig: (state, action: PayloadAction<any>) => {
      state.uaGroupInfo.config = { ...state.uaGroupInfo.config, ...action.payload }
    },
    setDps(state, action: PayloadAction<any>) {
      const dpKeys = Object.keys(action.payload)
      dpKeys.forEach(dp => {
        state.deviceInfo.dps[dp] = action.payload[dp]
      })
    },
    setGroupDps(state, action: PayloadAction<any>) {
      const dpKeys = Object.keys(action.payload)
      dpKeys.forEach(dp => {
        state.uaGroupInfo.dps[dp] = action.payload[dp]
      })
    },
    setTimeSchedule(state, action: PayloadAction<any>) {
      state.timeSchedule = action.payload
    },
    setEnergieverbrauch(state, action: PayloadAction<any>) {
      state.energieverbrauch = action.payload
    },
    setMoods(state, action: PayloadAction<any>) {
      state.moods = action.payload
    },
    setFlags(state, action: PayloadAction<any>) {
      state.flags = action.payload
    },
    setFlagMode(state, action: PayloadAction<any>) {
      state.flagModeState = action.payload
    },
    setGroupDevices(state, action: PayloadAction<any>) {
      state.uaGroupInfo.groupDevices = action.payload
    },
    setSystemTimeFormat(state, action: PayloadAction<any>) {
      state.is24HourClock = action.payload
    },
    setTimeZone(state, action: PayloadAction<any>) {
      state.timeZone = action.payload
    }
  },
})

type AsyncBlockType<T> = () => Promise<DpsResult<T>>
type SyncBlockType<T> = () => DpsResult<T>

function asyncSetDps<T>(dispatch: Dispatch<any>, block: AsyncBlockType<T> | SyncBlockType<T>): Promise<Result<T>> {
  return new Promise<Result<T>>(async (res, _rej) => {
    const dpsResult = await block()
    if (dpsResult.result.success) {
      // 只在结果成功时才改变 redux 数据
      dispatch(setDps(dpsResult.dps))
    }
    res(dpsResult.result)
  })
}

function simpleSetDps<T>(dispatch: Dispatch<any>): (deviceId: string, dps: any) => Promise<Result<T>> {
  return (deviceId, dps) => {
    return asyncSetDps(dispatch, async () => {
      console.log('发送dp数据：', deviceId, JSON.stringify(dps))
      const res = await NativeApi.setDps(deviceId, dps)
      if (res.result) {
        return {
          result: { success: true },
          dps: dps,
        }
      }
      return {
        result: { success: false, msg: res.msg },
      }
    })
  }
}

function simpleSetDp<T>(dispatch: Dispatch<any>): (deviceId: string, dp: string, value: any) => Promise<Result<T>> {
  return (deviceId, dp, value) => {
    const fun = simpleSetDps<T>(dispatch)
    return fun(deviceId, { [dp]: value })
  }
}

const useDeviceId = () => {
  return useSelector(store => store.ldvModules.deviceInfo.devId)
}

const useGroupId = () => {
  return useSelector(store => store.ldvModules.uaGroupInfo.tyGroupId)
}

const useFamilyName: () => string = () => {
  return useSelector(store => store.ldvModules.familyName)
}

const useRole: () => 0 | 1 | 2 | 3 = () => {
  return useSelector(store => store.ldvModules.role)
}

function useDp<T, R extends any>(dp: string): [T, (value: T) => Promise<Result<R>>] {
  const deviceId = useDeviceId()
  const dispatch = useDispatch()
  const dpValue = useSelector(store => store.ldvModules.deviceInfo.dps[dp])
  const setDp = useCallback((dpValue: T) => {
    return simpleSetDp<R>(dispatch)(deviceId, dp, dpValue)
  }, [])
  return [dpValue, setDp]
}

function useGroupDp<T>(dp: string): [T] {
  const dpValue = useSelector(store => store.ldvModules.uaGroupInfo.dps[dp])
  return [dpValue]
}

function useScaledDp<R extends any>(dp: string, scaled: number = 10): [number, (value: number) => Promise<Result<R>>] {
  const [sd, setSd] = useDp<number, R>(dp)
  const setScaledDpValue = useCallback(async (value: number) => {
    return await setSd(value * scaled)
  }, [setSd])
  return [sd / scaled, setScaledDpValue]
}

function useDps<R>(): [any, (dps: any) => Promise<Result<R>>] {
  const deviceId = useDeviceId()
  const dispatch = useDispatch()
  const dps = useSelector(store => store.ldvModules.deviceInfo.dps)
  const setDps = useCallback((dps: any) => {
    return simpleSetDps<R>(dispatch)(deviceId, dps)
  }, [])
  return [dps, setDps]
}

interface DpState {
  switch: boolean;

  [dpCode: string]: DpValue;
}

const useDeviceInfo: () => DevInfo<DpState> = () => {
  return useSelector(state => state.devInfo)
}

const useTimeSchedule = (): [v: boolean | undefined, f: any] => {
  const dps = useSelector(store => store.ldvModules.timeSchedule)
  const dispatch = useDispatch()
  const setTimeScheduleFn = (value: boolean) => {
    dispatch(setTimeSchedule(value))
  }
  return [dps, setTimeScheduleFn]
}

const useMoods = (): [any[], (v: any[]) => void] => {
  const dps = useSelector(store => store.ldvModules.moods)
  const dispatch = useDispatch()
  const setMoodsFn = (value: any[]) => {
    dispatch(setMoods(value))
  }
  return [dps, setMoodsFn]
}

const useFlags = (): [any[], (v: any[]) => void] => {
  const dps = useSelector(store => store.ldvModules.flags)
  const dispatch = useDispatch()
  const setFlagsFn = (value: any[]) => {
    dispatch(setFlags(value))
  }
  return [dps, setFlagsFn]
}

const useFlagMode = (): [FlagModeState, (v: FlagModeState) => void] =>{
  const dps = useSelector(store => store.ldvModules.flagModeState)
  const dispatch = useDispatch()
  const setFlagModeFn = (value: FlagModeState) => {
    dispatch(setFlagMode(value))
  }
  return [dps, setFlagModeFn]
}

const useGroupDevices = (): [GroupDeviceInfo[], (v: GroupDeviceInfo[]) => void] => {
  const dps = useSelector(store => store.ldvModules.uaGroupInfo.groupDevices)
  const dispatch = useDispatch()
  const setGroupDevicesFn = (value: GroupDeviceInfo[]) => {
    dispatch(setGroupDevices(value))
  }
  return [dps, setGroupDevicesFn]
}

const useSystemTimeFormate = (): boolean => {
  return useSelector(store => store.ldvModules.is24HourClock)
}

const useTimeZone = (): string => {
  return useSelector(store => store.ldvModules.timeZone)
}

const useTimeZoneCity = (): string => {
  return useSelector(store => store.ldvModules.timeZone).split('/')[1]
}

const useEnergieverbrauch = () => {
  const dps = useSelector(store => store.ldvModules.energieverbrauch)
  const dispatch = useDispatch()
  const setEnergieverbrauchFn = (value) => {
    dispatch(setEnergieverbrauch(value))
  }
  return [dps, setEnergieverbrauchFn]
}

export const useEngergyGeneration = () => {
  const { productId } = useDeviceInfo()
  return productList.some(val => val === productId)
}

export function useUAGroupInfo(): UAGroupInfo {
  return useSelector(state => state.ldvModules.uaGroupInfo)
}

export function putGroupTYDps(tyGroupId: number, dps: Record<string, any>) {
  return NativeApi.putGroupTYDps(tyGroupId, JSON.stringify(dps))
}

export function useGroupEzvizConfig<GC, T extends PropertyValueTypes<GC>>(featureKey: keyof GC, defConfig?: T): [T, (newConfig: T) => Promise<Result<any>>] {
  const tyGroupId = useUAGroupInfo().tyGroupId
  const config = useUAGroupInfo().config || {}
  const dispatch = useDispatch()
  const { setGroupConfig } = nativePropsSlice.actions
  const setConfig = useCallback(async (newConfig: T, extraConfig?: any) => {
    const configs = {
      [featureKey]: newConfig,
      ...(extraConfig || {}),
    }
    const res = await NativeApi.putGroupEzvizConfig(tyGroupId, JSON.stringify(configs))
    if (res.success) {
      // 只在结果成功时才改变 redux 数据
      dispatch(setGroupConfig(configs))
    }
    return res
  }, [])
  return [config[featureKey] ?? defConfig, setConfig]
}

export function useGroupConfig<T>(): [T, (dps: any, newConfig: T) => Promise<Result<any>>] {
  const tyGroupId = useUAGroupInfo().tyGroupId
  const config = useUAGroupInfo().config || {}
  const dispatch = useDispatch()
  const { setGroupConfig } = nativePropsSlice.actions
  const setConfig = useCallback(async (dps: any, newConfig: T) => {
    // 发送控制命令
    const res = await NativeApi.groupControl(tyGroupId, JSON.stringify(dps), JSON.stringify(newConfig))
    if (res.success) {
      // 只在结果成功时才改变 redux 数据
      dispatch(setGroupConfig(newConfig))
    }
    return res
  }, [])
  return [config, setConfig]
}

/**
 * @template GC GroupConfig
 * @template GCPT GroupConfigPropertyType
 * @param key
 * @param dpKey
 */
export function useGroupConfigFeature<GC, GCPT extends PropertyValueTypes<GC>>
(key: keyof GC, dpKey?: string): [GCPT | undefined, (value: GCPT, dpValue: any, extraDps?: any, extraConfig?: any) => Promise<Result<any>>] {
  const [groupConfig, setGroupConfig] = useGroupConfig<GC>()
  const setGroupConfigFeature = async (value: GCPT, dpValue: any, extraDps?: any, extraConfig?: any) => {
    const sendDpKey = !!dpKey ? dpKey : GlobalParams.dpSchemaMap[snakeCase(key as string)]?.dp

    const sendDps = {}
    if (sendDpKey) {
      sendDps[sendDpKey] = dpValue
    }

    return await setGroupConfig(
      {
        ...sendDps,
        ...(extraDps || {}),
      },
      {
        ...(extraConfig || {}),
        [key]: value,
      },
    )
  }
  return [groupConfig[key] as (GCPT | undefined), setGroupConfigFeature]
}

export function useFeatureHook<GC, T extends PropertyValueTypes<GC>>(
  featureKey: keyof GC,
  defValue: T,
  valueMapToDpValue?: (v: T) => any,
  getExtraDps?: (v: T) => any,
  getExtraConfig?: (v: T) => any,
): [T, (value: T) => Promise<Result<any>>] {
  const [featureHook, setFH] = useGroupConfigFeature<GC, T>(featureKey)
  const setFeatureHook = useCallback(async (value: T, extraDps?: any, extraConfig?: any) => {
    const dpValue = valueMapToDpValue ? valueMapToDpValue(value) : value
    const defDps = getExtraDps ? getExtraDps(value) : undefined
    const dps = {...(defDps || {}), ...(extraDps || {})}
    const defConfig = getExtraConfig ? getExtraConfig(value) : undefined
    const config = {...(defConfig || {}), ...(extraConfig || {})}
    return await setFH(value, dpValue, dps, config)
  }, [setFH])
  return [featureHook ?? defValue, setFeatureHook]
}

export const useFanMaxSpeed = () => {
  const { productId } = useDeviceInfo()
  return fanProductList.includes(productId) ? 20 : 3
}

export const isUVCFanDevice = () => {
  const { productId } = useDeviceInfo()
  return fanProductList.includes(productId)
}

export const ldvModules = nativePropsSlice.reducer

export const {
  setNativeProps,
  setGroupNativeProps,
  setDps,
  setGroupDps,
  setTimeSchedule,
  setMoods,
  setFlags,
  setFlagMode,
  setGroupDevices,
  setSystemTimeFormat,
  setTimeZone,
  setEnergieverbrauch,
} = nativePropsSlice.actions

export {
  asyncSetDps,
  simpleSetDps,
  simpleSetDp,
  useDeviceId,
  useDeviceInfo,
  useDp,
  useGroupId,
  useGroupDp,
  useScaledDp,
  useDps,
  useFamilyName,
  useRole,
  useTimeSchedule,
  useMoods,
  useFlags,
  useFlagMode,
  useGroupDevices,
  useSystemTimeFormate,
  useTimeZone,
  useTimeZoneCity,
  useEnergieverbrauch,
}
