package cache

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common"
	dt "github.com/fiskerinc/cloud-services/pkg/dbc/state"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/redis"
	"github.com/fiskerinc/cloud-services/pkg/utils/querystring"

	redigo "github.com/gomodule/redigo/redis"
	"github.com/pkg/errors"
)

const UPDATED_TIME_FORMAT = "2006-01-02T15:04:05Z"

type stateParser func(state *common.CarState, key string, value []byte) error

func NewVehicleState(client redis.ClientPoolInterface) *VehicleState {
	return &VehicleState{client: client}
}

type VehicleState struct {
	client redis.ClientPoolInterface
}

func (v *VehicleState) Get(vin string) (common.CarState, error) {
	var state common.CarState

	values, err := v.queryVehicleState(vin)
	if err != nil {
		return state, err
	}

	state, err = v.ParsePayloadForVehicleState(values)
	if err != nil {
		return state, err
	}

	return state, nil
}

func (v *VehicleState) queryVehicleState(vin string) ([]interface{}, error) {
	var payload []interface{}
	client := v.client.GetFromPool()
	defer client.Close()

	batch := redis.NewRedisBatchCommands()
	batch.Add("SISMEMBER", redis.CarSessionsKey(), vin)
	batch.Add("SISMEMBER", redis.HMISessionsKey(), vin)
	batch.Add("HGETALL", redis.CarStateHashKey(vin))

	payload, err := redigo.Values(client.ExecuteBatch(batch))
	if err != nil {
		return payload, errors.WithStack(err)
	}

	return payload, nil
}

func (v *VehicleState) ParsePayloadForVehicleState(payload []interface{}) (common.CarState, error) {
	var state common.CarState

	if len(payload) != 3 {
		return state, redis.ErrInvalidResults
	}

	online, err := redigo.Bool(payload[0], nil)
	if err != nil {
		return state, err
	} else {
		state.Online = online
	}

	online, err = redigo.Bool(payload[1], nil)
	if err != nil {
		return state, errors.WithStack(err)
	} else {
		state.OnlineHMI = online
	}

	err = v.parseCarStatePayload(&state, payload[2])

	return state, err
}

func (v *VehicleState) parseCarStatePayload(state *common.CarState, payload interface{}) error {
	stateValues, err := redigo.Values(payload, nil)
	if err != nil {
		return errors.WithStack(err)
	}
	if len(stateValues)%2 != 0 {
		return errors.New("object does not contain equal number of key value pairs")
	}

	err = v.parseStateValues(state, stateValues, parseCarState)

	return err
}

func (v *VehicleState) parseStateValues(state *common.CarState, stateValues []interface{}, parser stateParser) error {
	for i := 0; i < len(stateValues); i += 2 {
		key, okKey := stateValues[i].([]byte)
		value, okValue := stateValues[i+1].([]byte)

		if !okKey || !okValue {
			return errors.New("cannot parse object into car state")
		}

		err := parser(state, string(key), value)
		// log error, do not return error so we can read other properties for digital twin
		if err != nil {
			logger.Err(err).Send()
		}
	}

	return nil
}

func parseCarState(state *common.CarState, key string, value []byte) error {
	var err error
	val := string(value)

	switch key {
	case dt.VCU_VehChrgDchgMod:
		state.GetVCU0x260().ChargeType = string(value)
	case dt.BMS_Bat_SoC_usable:
		state.GetStateOfCharge().Usable, err = strconv.Atoi(val)
	case dt.BMS_Bat_SOH:
		state.GetStateOfCharge().Health, err = strconv.Atoi(val)
	case dt.BCM_AP_FL_LeFrntWinPosnInfo:
		state.GetWindows().LeftFront, err = strconv.Atoi(val)
	case dt.BCM_AP_FL_RiFrntWinPosnInfo:
		state.GetWindows().RightFront, err = strconv.Atoi(val)
	case dt.BCM_AP_FL_LeReWinPosnInfo:
		state.GetWindows().LeftRear, err = strconv.Atoi(val)
	case dt.BCM_AP_FL_RiReWinPosnInfo:
		state.GetWindows().RightRear, err = strconv.Atoi(val)
	case dt.BMS_PwrBattRmngCpSOC:
		state.GetBattery().Percent, err = strconv.Atoi(val)
	case dt.BCM_ReDefrstHeatgCmd:
		state.GetRearDefrost().On, err = strconv.ParseBool(val)
	case dt.BCM_PasFrntDoorSts:
		state.GetDoors().RightFront, err = strconv.ParseBool(val)
	case dt.BCM_DrFrntDoorSts:
		state.GetDoors().LeftFront, err = strconv.ParseBool(val)
	case dt.BCM_FrntDrDoorLockSts:
		state.GetLocks().Driver, err = notValue(strconv.ParseBool(val))
	case dt.BCM_CenLockSwtSts:
		state.GetLocks().All = (val == "2")
	case dt.BCM_RiReDoorSts:
		state.GetDoors().RightRear, err = strconv.ParseBool(val)
	case dt.BCM_LeReDoorSts:
		state.GetDoors().LeftRear, err = strconv.ParseBool(val)
	case dt.BCM_FrntHoodLidSts:
		state.GetDoors().Hood, err = strconv.ParseBool(val)
	case dt.PLGM_TrSts:
		state.GetDoors().Trunk, err = strconv.ParseBool(val)
	case dt.BCM_SunroofPosnInfo:
		state.GetSunroof().Sunroof, err = strconv.Atoi(val)
	case dt.BCM_AP_TL_LeReWinPosnInfo:
		state.GetMiscWindows().LeftRearQuarter, err = strconv.Atoi(val)
	case dt.BCM_AP_TL_RiReWinPosnInfo:
		state.GetMiscWindows().RightRearQuarter, err = strconv.Atoi(val)
	case dt.BCM_AP_RW_WinPosnInfo:
		state.GetMiscWindows().RearWindshield, err = strconv.Atoi(val)
	case dt.BMS_BattAvrgT:
		state.GetCellTemperature().AvgBatteryTemp, err = strconv.Atoi(val)
	case dt.ECC_OutdT:
		state.GetAmbientTemperature().Temperature, err = strconv.Atoi(val)
	case dt.BCM_HeatedSteerWhlSt:
		state.GetSteeringWheelHeat().On, err = strconv.ParseBool(val)
	case dt.ESP_VehSpd:
		state.GetVehicleSpeed().Speed, err = strconv.ParseFloat(val, 64)
	case dt.VCU_DrvgMilg:
		state.GetMaxRange().MaxMiles, err = strconv.Atoi(val)
	case dt.PSM_PassSeatHeatgSts:
		state.GetPassengerSeatHeat().Level, err = strconv.Atoi(val)
	case dt.DSMC_DrvrSeatHeatgSts:
		state.GetDriverSeatHeat().Level, err = strconv.Atoi(val)
	case dt.ICC_TotMilg_ODO:
		state.GetBattery().TotalMileageOdometer, err = querystring.ConvertStringToInt(val)
	case dt.VCU_DCChrgRmngTi, dt.BMS_RmChrgTi_TrgtSoC:
		state.GetChargingMetrics().RemainingChargingTime, err = strconv.Atoi(val)
	case dt.IBS_BatteryVoltage:
		state.GetBattery().BatteryVoltage, err = strconv.ParseFloat(val, 64)
		state.GetBattery12V().IBS_BatteryVoltage = ref(state.GetBattery().BatteryVoltage)
	case dt.VCU_GearSig:
		var gear int
		gear, err = strconv.Atoi(val)
		state.GetGear().InPark = (gear <= 2)
	case dt.BMS_RmChrgTi_FullChrg:
		state.GetChargingMetrics().RemainingChargingTimeFull, err = strconv.Atoi(val)
	case dt.ECC_InsdT:
		state.GetCabinClimate().InternalTemperature, err = strconv.Atoi(val)
	case dt.ECC_RemTSetSts:
		state.GetCabinClimate().CabinTemperature, err = strconv.Atoi(val)
	case dt.TBOX_GPSHei:
		state.GetLocation().Altitude, err = strconv.ParseFloat(val, 64)
	case dt.TBOX_GPSLongi:
		state.GetLocation().Longitude, err = strconv.ParseFloat(val, 64)
	case dt.TBOX_GPSLati:
		state.GetLocation().Latitude, err = strconv.ParseFloat(val, 64)
	case dt.DBC_VERSION:
		state.DBCVersion = val
	case dt.TREX_VERSION:
		state.TRexVersion = val
	case dt.TREX_IP:
		state.IP = val
	case dt.UPDATED_AT:
		var t time.Time
		t, err = time.Parse(UPDATED_TIME_FORMAT, strings.Trim(val, "\""))
		if !t.IsZero() {
			state.UpdatedAt = ref(t)
		}
	case dt.VCU_VehSt:
		state.GetSafeState().VehicleSafeState = val == dt.VCU_VehSt_Safestate
	case dt.VCU_VcuState:
		state.GetSafeState().VCUSafeState = val == dt.VCU_VcuState_Safestate
	case dt.MCU_F_ActSafeSt:
		state.GetSafeState().MCUFrontSafeState = val == dt.MCU_F_ActSafeSt_AS0 || val == dt.MCU_F_ActSafeSt_ASC || val == dt.MCU_F_ActSafeSt_ASC_Emergency
	case dt.MCU_R_ActSafeSt:
		state.GetSafeState().MCURearSafeState = val == dt.MCU_R_ActSafeSt_AS0 || val == dt.MCU_R_ActSafeSt_ASC || val == dt.MCU_R_ActSafeSt_ASC_Emergency
	case dt.MCU_R_Decoup_State:
		state.GetSafeState().MCURearDecoupState = val == dt.MCU_R_Decoup_State_Connected
	case dt.MCU_F_CrtMod:
		state.GetSafeState().MCUFrontInverterError = val == dt.MCU_F_CrtMod_Internal_inverter_error || val == dt.MCU_F_CrtMod_Invalid
	case dt.MCU_R_CrtMod:
		state.GetSafeState().MCURearInverterError = val == dt.MCU_R_CrtMod_Internal_inverter_error || val == dt.MCU_R_CrtMod_Invalid
	case dt.ACU_Drvr_Occpt_St:
		var vi int
		vi, err = strconv.Atoi(val)
		state.DriverOccupySeatState = ref(vi)
	case dt.BCM_PwrMod:
		var vi int
		vi, err = strconv.Atoi(val)
		state.PowerMode = ref(vi)
	case dt.PWC_ChrgSts:
		var vi int
		vi, err = strconv.Atoi(val)
		state.ChargingStatus = ref(vi)
	case dt.VCU_RdyLamp:
		state.GetVehicleReadyState().IsVehicleReady, err = strconv.ParseBool(val)
	// New untested signals
	// case dt.IBS_SOCUpperTolerance:
	// 	var vi float64
	// 	vi, err = strconv.ParseFloat(val, 64)
	// 	state.GetExpandedSignals().IBS_SOCUpperTolerance = ref(vi)
	// case dt.IBS_SOCLowerTolerance:
	// 	var vi float64
	// 	vi, err = strconv.ParseFloat(val, 64)
	// 	state.GetExpandedSignals().IBS_SOCLowerTolerance = ref(vi)
	case dt.IBS_StateOfCharge:
		var vi float64
		vi, err = strconv.ParseFloat(val, 64)
		state.GetBattery12V().IBS_StateOfCharge = ref(vi)
	case dt.IBS_StateOfHealth:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetBattery12V().IBS_StateOfHealth = ref(vi)
	case dt.IBS_NominalCapacity:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetExpandedSignals().IBS_NominalCapacity = ref(vi)
	case dt.IBS_AvailableCapacity:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetExpandedSignals().IBS_AvailableCapacity = ref(vi)
	case dt.BCM_TotMilg_ODO:
		var vi float64
		vi, err = strconv.ParseFloat(val, 64)
		state.GetExpandedSignals().BCM_TotMilg_ODO = ref(vi)
	case dt.BMS_SwVersS:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetExpandedSignals().BMS_SwVersS = ref(vi)
	case dt.BMS_SwVersM:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetExpandedSignals().BMS_SwVersM = ref(vi)
	case dt.BMS_SwVers:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetExpandedSignals().BMS_SwVers = ref(vi)
	case dt.BMS_AccueDchaTotAh:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetExpandedSignals().BMS_AccueDchaTotAh = ref(vi)
	case dt.BMS_AccueChrgTotAh:
		var vi int
		vi, err = strconv.Atoi(val)
		state.GetExpandedSignals().BMS_AccueChrgTotAh = ref(vi)
	case dt.TBOX_Heading:
		state.GetLocation().Heading, err = strconv.ParseFloat(val, 64)
	case dt.PKC_KeyStsMod:
		state.GetGear().Immobilizer = val
	}

	return errors.WithStack(err)
}

func ParseCarState(state *common.CarState, key string, value interface{}) (err error) {
	found := false
	ok := false
	switch key {
	case dt.VCU_VehChrgDchgMod:
		found = true
		state.GetVCU0x260().ChargeType = value.(string)
	case dt.BMS_Bat_SoC_usable:
		found = true
		state.GetStateOfCharge().Usable, ok = value.(int)
	case dt.BMS_Bat_SOH:
		found = true
		state.GetStateOfCharge().Health, ok = value.(int)
	case dt.BCM_AP_FL_LeFrntWinPosnInfo:
		found = true
		state.GetWindows().LeftFront, ok = value.(int)
	case dt.BCM_AP_FL_RiFrntWinPosnInfo:
		found = true
		state.GetWindows().RightFront, ok = value.(int)
	case dt.BCM_AP_FL_LeReWinPosnInfo:
		found = true
		state.GetWindows().LeftRear, ok = value.(int)
	case dt.BCM_AP_FL_RiReWinPosnInfo:
		found = true
		state.GetWindows().RightRear, ok = value.(int)
	case dt.BMS_PwrBattRmngCpSOC:
		found = true
		state.GetBattery().Percent, ok = value.(int)
	case dt.BCM_ReDefrstHeatgCmd:
		found = true
		state.GetRearDefrost().On, ok = value.(bool)
	case dt.BCM_PasFrntDoorSts:
		found = true
		state.GetDoors().RightFront, ok = value.(bool)
	case dt.BCM_DrFrntDoorSts:
		found = true
		state.GetDoors().LeftFront, ok = value.(bool)
	case dt.BCM_FrntDrDoorLockSts:
		found = true
		var vv bool
		vv, ok = value.(bool)
		state.GetLocks().Driver = !vv
	case dt.BCM_CenLockSwtSts:
		found = true
		state.GetLocks().All = strconv.Itoa(value.(int)) == "2"
	case dt.BCM_RiReDoorSts:
		found = true
		state.GetDoors().RightRear, ok = value.(bool)
	case dt.BCM_LeReDoorSts:
		found = true
		state.GetDoors().LeftRear, ok = value.(bool)
	case dt.BCM_FrntHoodLidSts:
		found = true
		state.GetDoors().Hood, ok = value.(bool)
	case dt.PLGM_TrSts:
		found = true
		state.GetDoors().Trunk, ok = value.(bool)
	case dt.BCM_SunroofPosnInfo:
		found = true
		state.GetSunroof().Sunroof, ok = value.(int)
	case dt.BCM_AP_TL_LeReWinPosnInfo:
		found = true
		state.GetMiscWindows().LeftRearQuarter, ok = value.(int)
	case dt.BCM_AP_TL_RiReWinPosnInfo:
		found = true
		state.GetMiscWindows().RightRearQuarter, ok = value.(int)
	case dt.BCM_AP_RW_WinPosnInfo:
		found = true
		state.GetMiscWindows().RearWindshield, ok = value.(int)
	case dt.BMS_BattAvrgT:
		found = true
		state.GetCellTemperature().AvgBatteryTemp, ok = value.(int)
	case dt.ECC_OutdT:
		found = true
		state.GetAmbientTemperature().Temperature, ok = value.(int)
	case dt.BCM_HeatedSteerWhlSt:
		found = true
		state.GetSteeringWheelHeat().On, ok = value.(bool)
	case dt.ESP_VehSpd:
		found = true
		state.GetVehicleSpeed().Speed, ok = value.(float64)
	case dt.VCU_DrvgMilg:
		found = true
		state.GetMaxRange().MaxMiles, ok = value.(int)
	case dt.PSM_PassSeatHeatgSts:
		found = true
		state.GetPassengerSeatHeat().Level, ok = value.(int)
	case dt.DSMC_DrvrSeatHeatgSts:
		found = true
		state.GetDriverSeatHeat().Level, ok = value.(int)
	case dt.ICC_TotMilg_ODO:
		found = true
		// Seems wierd its sometimes an int, sometimes a float
		state.GetBattery().TotalMileageOdometer, ok = value.(int)
	case dt.VCU_DCChrgRmngTi, dt.BMS_RmChrgTi_TrgtSoC:
		found = true
		state.GetChargingMetrics().RemainingChargingTime, ok = value.(int)
	case dt.IBS_BatteryVoltage:
		found = true
		state.GetBattery().BatteryVoltage, ok = value.(float64)
		state.GetBattery12V().IBS_BatteryVoltage = ref(state.GetBattery().BatteryVoltage)
	case dt.VCU_GearSig:
		found = true
		var gear int
		gear, ok = value.(int)
		state.GetGear().InPark = (gear <= 2)
	case dt.BMS_RmChrgTi_FullChrg:
		found = true
		state.GetChargingMetrics().RemainingChargingTimeFull, ok = value.(int)
	case dt.ECC_InsdT:
		found = true
		state.GetCabinClimate().InternalTemperature, ok = value.(int)
	case dt.ECC_RemTSetSts:
		found = true
		state.GetCabinClimate().CabinTemperature, ok = value.(int)
	case dt.TBOX_GPSHei:
		found = true
		state.GetLocation().Altitude, ok = value.(float64)
	case dt.TBOX_GPSLongi:
		found = true
		state.GetLocation().Longitude, ok = value.(float64)
	case dt.TBOX_GPSLati:
		found = true
		state.GetLocation().Latitude, ok = value.(float64)
	case dt.DBC_VERSION:
		found = true
		state.DBCVersion = value.(string)
	case dt.TREX_VERSION:
		found = true
		state.TRexVersion = value.(string)
	case dt.TREX_IP:
		found = true
		state.IP = value.(string)
	case dt.UPDATED_AT:
		var t time.Time
		t, err = time.Parse(UPDATED_TIME_FORMAT, strings.Trim(value.(string), "\""))
		if !t.IsZero() {
			state.UpdatedAt = ref(t)
		}
	case dt.VCU_VehSt:
		found = true
		state.GetSafeState().VehicleSafeState = strconv.Itoa(value.(int)) == dt.VCU_VehSt_Safestate
	case dt.VCU_VcuState:
		found = true
		state.GetSafeState().VCUSafeState = strconv.Itoa(value.(int)) == dt.VCU_VcuState_Safestate
	case dt.MCU_F_ActSafeSt:
		found = true
		state.GetSafeState().MCUFrontSafeState = strconv.Itoa(value.(int)) == dt.MCU_F_ActSafeSt_AS0 || strconv.Itoa(value.(int)) == dt.MCU_F_ActSafeSt_ASC || strconv.Itoa(value.(int)) == dt.MCU_F_ActSafeSt_ASC_Emergency
	case dt.MCU_R_ActSafeSt:
		found = true
		state.GetSafeState().MCURearSafeState = strconv.Itoa(value.(int)) == dt.MCU_R_ActSafeSt_AS0 || strconv.Itoa(value.(int)) == dt.MCU_R_ActSafeSt_ASC || strconv.Itoa(value.(int)) == dt.MCU_R_ActSafeSt_ASC_Emergency
	case dt.MCU_R_Decoup_State:
		found = true
		state.GetSafeState().MCURearDecoupState = strconv.Itoa(value.(int)) == dt.MCU_R_Decoup_State_Connected
	case dt.MCU_F_CrtMod:
		found = true
		state.GetSafeState().MCUFrontInverterError = strconv.Itoa(value.(int)) == dt.MCU_F_CrtMod_Internal_inverter_error || strconv.Itoa(value.(int)) == dt.MCU_F_CrtMod_Invalid
	case dt.MCU_R_CrtMod:
		found = true
		state.GetSafeState().MCURearInverterError = strconv.Itoa(value.(int)) == dt.MCU_R_CrtMod_Internal_inverter_error || strconv.Itoa(value.(int)) == dt.MCU_R_CrtMod_Invalid
	case dt.ACU_Drvr_Occpt_St:
		found = true
		var vi int
		vi, ok = value.(int)
		state.DriverOccupySeatState = ref(vi)
	case dt.BCM_PwrMod:
		found = true
		var vi int
		vi, ok = value.(int)
		state.PowerMode = ref(vi)
	case dt.PWC_ChrgSts:
		found = true
		var vi int
		vi, ok = value.(int)
		state.ChargingStatus = ref(vi)
	case dt.VCU_RdyLamp:
		found = true
		state.GetVehicleReadyState().IsVehicleReady, ok = value.(bool)
	case "online":
		found = true
		state.Online, ok = value.(bool)
	case "online_hmi":
		found = true
		state.OnlineHMI, ok = value.(bool)
		// New untested signals
	// case dt.IBS_SOCUpperTolerance:
	// 	found = true
	// 	var vi float64
	// 	vi, ok = value.(float64)
	// 	state.GetExpandedSignals().IBS_SOCUpperTolerance = ref(vi)
	// case dt.IBS_SOCLowerTolerance:
	// 	found = true
	// 	var vi float64
	// 	vi, ok = value.(float64)
	// 	state.GetExpandedSignals().IBS_SOCLowerTolerance = ref(vi)
	case dt.IBS_StateOfCharge:
		found = true
		var vi float64
		vi, ok = value.(float64)
		state.GetBattery12V().IBS_StateOfCharge = ref(vi)
	case dt.IBS_StateOfHealth:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetBattery12V().IBS_StateOfHealth = ref(vi)
	case dt.IBS_NominalCapacity:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetExpandedSignals().IBS_NominalCapacity = ref(vi)
	case dt.IBS_AvailableCapacity:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetExpandedSignals().IBS_AvailableCapacity = ref(vi)
	case dt.BCM_TotMilg_ODO:
		found = true
		var vi float64
		vi, ok = value.(float64)
		state.GetExpandedSignals().BCM_TotMilg_ODO = ref(vi)
	case dt.BMS_SwVersS:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetExpandedSignals().BMS_SwVersS = ref(vi)
	case dt.BMS_SwVersM:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetExpandedSignals().BMS_SwVersM = ref(vi)
	case dt.BMS_SwVers:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetExpandedSignals().BMS_SwVers = ref(vi)
	case dt.BMS_AccueDchaTotAh:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetExpandedSignals().BMS_AccueDchaTotAh = ref(vi)
	case dt.BMS_AccueChrgTotAh:
		found = true
		var vi int
		vi, ok = value.(int)
		state.GetExpandedSignals().BMS_AccueChrgTotAh = ref(vi)
	case dt.TBOX_Heading:
		found = true
		state.GetLocation().Heading, ok = value.(float64)
	case dt.PKC_KeyStsMod:
		found = true
		state.GetGear().Immobilizer = value.(string)
	}
	if found {
		if !ok {
			err = fmt.Errorf("failed on key %s value %v", key, value)
		}
	} else {
		logger.Info().Str("key", key).Interface("value", value).Msgf("did not have parsing mode for key")
	}

	return errors.WithStack(err)
}

func ref[T any](v T) *T {
	return &v
}

func IsCarOnline(clientPool redis.ClientPoolInterface, vin string) (bool, error) {
	client := clientPool.GetFromPool()
	defer client.Close()
	return redigo.Bool(
		client.Execute("SISMEMBER", redis.CarSessionsKey(), vin),
	)
}

func notValue(value bool, err error) (bool, error) {
	return !value, err
}
