package cachev2

import (
	"context"
	"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"
	redis "github.com/fiskerinc/cloud-services/pkg/redisv2"
	"github.com/fiskerinc/cloud-services/pkg/utils/querystring"
	redispkg "github.com/redis/go-redis/v9"

	"github.com/pkg/errors"
)

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

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

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

type VehicleState struct {
	redisClient redis.ClientInterface
}

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
}

type QueryVehicleStateResponse struct {
	CarSessionExists bool
	HMISessionExists bool
	CarState         map[string]string
}

type QueryVehicleStatePreResponse struct {
	CarSessionExists *redispkg.BoolCmd
	HMISessionExists *redispkg.BoolCmd
	CarState         *redispkg.MapStringStringCmd
}

func (qvspr *QueryVehicleStatePreResponse) Resolve() (qvsr *QueryVehicleStateResponse, errR error) {
	var err error
	qvsr = &QueryVehicleStateResponse{}
	qvsr.CarSessionExists, err = qvspr.CarSessionExists.Result()
	if err != nil {
		errR = errors.Wrap(errR, err.Error())
	}
	qvsr.HMISessionExists, err = qvspr.HMISessionExists.Result()
	if err != nil {
		errR = errors.Wrap(errR, err.Error())
	}
	qvsr.CarState, err = qvspr.CarState.Result()
	if err != nil {
		errR = errors.Wrap(errR, err.Error())
	}
	return
}

func (v *VehicleState) queryVehicleState(vin string) (QueryVehicleStateResponse, error) {
	payload := QueryVehicleStateResponse{}

	pipe := v.redisClient.GetClient().TxPipeline()

	carSessionKey := pipe.SIsMember(context.Background(), redis.CarSessionsKey(), vin)
	hmiSessionKey := pipe.SIsMember(context.Background(), redis.HMISessionsKey(), vin)
	carStateHash := pipe.HGetAll(context.Background(), redis.CarStateHashKey(vin))

	_, err := pipe.Exec(context.Background())
	if err != nil {
		return payload, errors.WithStack(err)
	}
	payload.CarSessionExists = carSessionKey.Val()
	payload.HMISessionExists = hmiSessionKey.Val()
	payload.CarState = carStateHash.Val()

	return payload, nil
}

func (v *VehicleState) ParsePayloadForVehicleState(payload QueryVehicleStateResponse) (common.CarState, error) {
	var state common.CarState
	state.Online = payload.CarSessionExists
	state.OnlineHMI = payload.HMISessionExists

	var err error
	err = v.parseStateValues(&state, payload.CarState, v.parseCarState)

	return state, err
}

func ParsePayloadForVehicleState(payload *QueryVehicleStateResponse) (state *common.CarState, err error) {
	state = &common.CarState{}
	state.Online = payload.CarSessionExists
	state.OnlineHMI = payload.HMISessionExists
	err = parseStateValues(state, payload.CarState, ParseCarState)
	return
}

func ParsePayloadForALVehicleState(payload *QueryVehicleStateResponse) (alState *common.CarStateAL, err error) {
	alState.CarState = &common.CarState{}
	alState.Online = payload.CarSessionExists
	alState.OnlineHMI = payload.HMISessionExists
	err = parseStateValues(alState.CarState, payload.CarState, ParseCarState)
	return
}

func (v *VehicleState) parseStateValues(state *common.CarState, stateValues map[string]string, parser stateParser) error {
	for key, value := range stateValues {

		_, err := parser(state, string(key), []byte(value))
		// log error, do not return error so we can read other properties for digital twin
		if err != nil {
			// strconv.Atoi: parsing "127.5": invalid syntax, track down. Add better info
			logger.Err(err).Send()
		}
	}

	return nil
}

func parseStateValues(state *common.CarState, stateValues map[string]string, parser stateParser) error {
	for key, value := range stateValues {

		_, err := parser(state, string(key), []byte(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 (v *VehicleState) parseCarState(state *common.CarState, key string, value []byte) (bool, error) {
	var err error
	val := string(value)

	switch key {
	case dt.VCU_VehChrgDchgMod:
		state.GetVCU0x260().ChargeType = val
	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 true, errors.WithStack(err)
}

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

	return found, errors.WithStack(err)
}

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

func IsCarOnline(client redis.ClientInterface, vin string) (bool, error) {
	res := client.GetClient().SIsMember(context.Background(), redis.CarSessionsKey(), vin)
	return res.Result()
}

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