package cachev2

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

	"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 (
	pattern = "car:*:state"
)

type DigitalTwinTimestampState struct {
	redisClient redis.Client
}

func NewDigitalTwinTimestampState(redisClient redis.Client) *DigitalTwinTimestampState {
	return &DigitalTwinTimestampState{
		redisClient: redisClient,
	}
}

// getStateKeys retrieves car state keys from Redis based on the specified pattern
// and returns a sliced list of keys according to the provided offset and limit.
//
// Parameters:
//   - offset: An integer indicating the starting index of the slice.
//   - limit: An integer specifying the maximum number of elements in the sliced list.
//
// Returns:
//   - []string: A sliced list of car state keys based on the given offset and limit.
//   - error: An error, if any, encountered during the Redis operation or slicing process.
func (dtts *DigitalTwinTimestampState) getStateKeys(offset, limit int) ([]string, error) {
	keys, err := redigo.Strings(dtts.redisClient.Execute("KEYS", pattern))
	if err != nil {
		return nil, err
	}
	totalKeys := len(keys)
	if totalKeys <= offset {
		return nil, nil
	}

	if (offset + limit) > totalKeys {
		limit = totalKeys - offset
	}

	sort.Strings(keys)

	keys = keys[offset : offset+limit]
	return keys, nil
}

// readCarStateByKey retrieves data from Redis based on the specified key using the HGETALL command.
// It iterates over all keys and values returned by the command, sets them in a response map,
// and returns the populated map along with any encountered errors.
//
// Parameters:
//   - key: A string representing the key to retrieve data from in Redis.
//
// Returns:
//   - map[string]interface{}: A map containing keys and values retrieved from Redis.
//   - error: An error, if any, encountered during the Redis HGETALL operation or mapping process.
func (dtts *DigitalTwinTimestampState) readCarStateByKey(key string) (map[string]interface{}, error) {

	keyval := make(map[string]interface{})
	batch := redis.NewRedisBatchCommands()

	batch.Add("HGETALL", key)

	payload, err := redigo.Values(dtts.redisClient.ExecuteBatch(batch))
	if err != nil {
		return keyval, err
	}

	stateValues, err := redigo.Values(payload[0], nil)
	if err != nil {
		return keyval, err
	}

	for i := 0; i < len(stateValues); i += 2 {
		key, okKey := stateValues[i].([]byte)
		value, okValue := stateValues[i+1].([]byte)

		if !okKey || !okValue {
			continue
		}

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

// GetDigitalTwinSignals retrieves digital twin signals from Redis based on the specified offset and limit.
// It reads all signals from Redis and returns a list of maps, where each map represents a cars signal with its properties.
//
// Parameters:
//   - offset: An integer indicating the starting index of the signals to retrieve.
//   - limit: An integer specifying the maximum number of signals to retrieve.
//
// Returns:
//   - []map[string]interface{}: A list of maps representing digital twin signals.

func (dtts *DigitalTwinTimestampState) GetDigitalTwinSignals(offset, limit int) (resp []map[string]interface{}) {

	keys, err := dtts.getStateKeys(offset, limit)
	if err != nil {
		logger.Warn().Err(err).Send()
		return
	}
	for _, key := range keys {
		keyval, err := dtts.readCarStateByKey(key)
		if err != nil {
			logger.Warn().Err(err).Send()
			continue
		}
		if len(keyval) > 0 {
			keySlice := strings.Split(key, ":")
			keyval["VIN"] = keySlice[1]
			resp = append(resp, keyval)
		}
	}
	return
}

// timestampKey generates a timestamp key based on the provided key by appending ":updated" to it.
// It formats the key in a way suitable for storing timestamps associated with the original key in data storage systems.
//
// Parameters:
//   - key: A string representing the original key for which the timestamp key is generated.
//
// Returns:
//   - string: A formatted string representing the timestamp key.
func (dtts *DigitalTwinTimestampState) timestampKey(key string) string {
	return fmt.Sprintf("%s:%s", key, "updated")
}

// timestampVal parses a byte slice containing a JSON-encoded timestamp and returns a pointer to a time.Time
// representing the parsed timestamp. It uses the UnmarshalJSON method of the time.Time type for decoding.
//
// Parameters:
//   - val: A byte slice containing the JSON-encoded timestamp to be parsed.
//
// Returns:
//   - time.Time: A time representing the parsed timestamp.
//   - error: An error, if any, encountered during the parsing process.
func (dtts *DigitalTwinTimestampState) timestampVal(val []byte) (time.Time, error) {
	t := &time.Time{}
	err := t.UnmarshalJSON(val)
	return *t, err
}

// parseCarState checks if the provided key is needed and, if so, sets the key and value in the given map.
//
// Parameters:
//   - key: A string representing the key to check and potentially set in the map.
//   - value: A byte slice containing the value associated with the key.
//   - keyval: A map[string]interface{} where the key and valueset if the key is needed.
//
// Returns:
//   - error: An error, if any, encountered during the parsing and mapping process.
func (dtts *DigitalTwinTimestampState) parseCarState(key string, value []byte, keyval map[string]interface{}) error {
	var err error
	val := string(value)
	switch key {
	case state.BMS_PwrBattRmngCpSOC, state.BMS_RmChrgTi_FullChrg, state.BCM_PwrMod, state.PWC_ChrgSts, state.VCU_DCChrgRmngTi, state.BMS_RmChrgTi_TrgtSoC:
		keyval[key], err = strconv.Atoi(val)
	case state.ICC_TotMilg_ODO:
		keyval[key], err = querystring.ConvertStringToInt(val)
	case state.IBS_BatteryVoltage:
		keyval[key], err = strconv.ParseFloat(val, 64)

	// updated timestamps
	case dtts.timestampKey(state.BMS_PwrBattRmngCpSOC), dtts.timestampKey(state.ICC_TotMilg_ODO), dtts.timestampKey(state.VCU_DCChrgRmngTi), dtts.timestampKey(state.BMS_RmChrgTi_TrgtSoC), dtts.timestampKey(state.IBS_BatteryVoltage),
		dtts.timestampKey(state.BMS_RmChrgTi_FullChrg), dtts.timestampKey(state.BCM_PwrMod), dtts.timestampKey(state.PWC_ChrgSts):
		keyval[key], err = dtts.timestampVal(value)
	}
	return errors.WithStack(err)
}
