package immobilizershared

import (
	"context"
	"encoding/json"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/redisv2"
)

// Grabs all the vehicle statuses from redis
func ReadVehicleStatuses(connection *redisv2.Connection) (results map[string]ImmobilizeTrack, err error) {
	res := connection.HGetAll(context.Background(), IMMOBILIZER_HASHSET_NAME)
	tempMap, err := res.Result()
	if err != nil {
		logger.Err(err).Stack().Str("function", "ReadVehicleStatuses").Msg("Immobilizer Error")
		return
	}

	results = make(map[string]ImmobilizeTrack)
	for key, value := range tempMap {
		tempTrack := ImmobilizeTrack{}
		err = json.Unmarshal([]byte(value), &tempTrack)
		if err != nil {
			logger.Err(err).Stack().Str("function", "ReadVehicleStatuses").Msg("Immobilizer Error")
			continue
		}
		results[key] = tempTrack
	}
	return
}

// WE want to update redis with the fact a car is immobilized, so start with modifying redis, then sending a publish event
// This kind of specification per function is sort of silly, probably will want to change in the future
func UpdateCarImmobilized(vin string, connection *redisv2.Connection) (err error) {
	err = SetVehicleImmobilized(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarImmobilized").Msg("Immobilizer Error")
		return
	}
	err = PublishVehicleImmobilized(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarImmobilized").Msg("Immobilizer Error")
		return
	}
	return
}

func UpdateCarImmobilizing(vin string, connection *redisv2.Connection) (err error) {
	err = SetVehicleImmobilizing(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarImmobilizing").Msg("Immobilizer Error")
		return
	}
	err = PublishVehicleImmobilizing(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarImmobilizing").Msg("Immobilizer Error")
		return
	}
	return
}

func UpdateCarMobilized(vin string, connection *redisv2.Connection) (err error) {
	err = SetVehicleMobilized(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarMobilized").Msg("Immobilizer Error")
		return
	}
	err = PublishVehicleMobilized(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarMobilized").Msg("Immobilizer Error")
		return
	}
	return
}

func UpdateCarMobilizing(vin string, connection *redisv2.Connection) (err error) {
	err = SetVehicleMobilizing(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarMobilizing").Msg("Immobilizer Error")
		return
	}
	err = PublishVehicleMobilizing(vin, connection)
	if err != nil {
		logger.Err(err).Stack().Str("function", "UpdateCarMobilizing").Msg("Immobilizer Error")
		return
	}
	return
}

// Might not need to call these directly
// Publishing the fact the car is now mobilized. Ditto's will remove the car from their containers
func PublishVehicleMobilized(vin string, connection *redisv2.Connection) (err error) {
	return PublishVehicleMobilizationChange(RedisImmobilizerMsg{VIN: vin, State: STATE_MOBILIZED}, connection)
}

func PublishVehicleMobilizing(vin string, connection *redisv2.Connection) (err error) {
	return PublishVehicleMobilizationChange(RedisImmobilizerMsg{VIN: vin, State: STATE_MOBILIZING}, connection)
}

func PublishVehicleImmobilized(vin string, connection *redisv2.Connection) (err error) {
	return PublishVehicleMobilizationChange(RedisImmobilizerMsg{VIN: vin, State: STATE_IMMOBILIZED}, connection)
}

// Publish the fact we are trying to immobile the vehicle
func PublishVehicleImmobilizing(vin string, connection *redisv2.Connection) (err error) {
	return PublishVehicleMobilizationChange(RedisImmobilizerMsg{VIN: vin, State: STATE_IMMOBILIZING}, connection)
}

func PublishVehicleMobilizationChange(msg RedisImmobilizerMsg, connection *redisv2.Connection) (err error) {
	// I think the int that is returned is the number of subscribers
	res := connection.Publish(context.Background(), IMMOBILIZER_PUBSUB_CHANNEL, msg)
	err = res.Err()
	return
}

// These all modify the redis data store, which acts as a database for the cars that are immobilized
func SetVehicleMobilized(vin string, connection *redisv2.Connection) (err error) {
	// Instead of using HSET, we are deleting the car from the immobilized stats line. Keeps the redis store smaller
	res := connection.HDel(context.Background(), IMMOBILIZER_HASHSET_NAME, vin)
	err = res.Err()
	if err != nil {
		logger.Err(err).Stack().Str("function", "SetVehicleMobilized").Msg("Immobilizer Error")
	}
	return
}

func SetVehicleMobilizing(vin string, connection *redisv2.Connection) (err error) {
	return setVehicleMobilizationChange(vin, ImmobilizeTrack{State: STATE_MOBILIZING}, connection)
}

func SetVehicleImmobilized(vin string, connection *redisv2.Connection) (err error) {
	return setVehicleMobilizationChange(vin, ImmobilizeTrack{State: STATE_IMMOBILIZED}, connection)
}

// Set the fact we are trying to immobile the vehicle
func SetVehicleImmobilizing(vin string, connection *redisv2.Connection) (err error) {
	return setVehicleMobilizationChange(vin, ImmobilizeTrack{State: STATE_IMMOBILIZING}, connection)
}

// CHANGE
func setVehicleMobilizationChange(vin string, msg ImmobilizeTrack, connection *redisv2.Connection) (err error) {
	// I think the int that is returned is the number of subscribers
	res := connection.HSet(context.Background(), IMMOBILIZER_HASHSET_NAME, vin, msg)
	err = res.Err()
	if err != nil {
		logger.Err(err).Stack().Str("function", "setVehicleMobilizationChange").Msg("Immobilizer Error")
		return
	}
	return
}

const IMMOBILIZER_PUBSUB_CHANNEL = "immobilizer"
const IMMOBILIZER_HASHSET_NAME = "immobilizer"

type ImmobilizeTrack struct {
	State ImmobilizeState
	// IDK, do we need the time we tried? We probably need a timer thing
	CreatedAt *time.Time
	UpdatedAt *time.Time
}

// For redis serialization
func (it ImmobilizeTrack) MarshalBinary() ([]byte, error) {
	return json.Marshal(it)
}

func (it ImmobilizeTrack) UnmarshalBinary(data []byte) error {
	return json.Unmarshal(data, &it)
}

type ImmobilizeState int

var (
	STATE_IMMOBILIZED  ImmobilizeState = 0 // Car officially immobilized
	STATE_IMMOBILIZING ImmobilizeState = 1 // Trying to immobilize car
	STATE_MOBILIZING   ImmobilizeState = 2 // Trying to mobilize the car
	STATE_MOBILIZED    ImmobilizeState = 3 // Car is mobilized, although not stored in redis or ditto, is used to communicate the change has been made
)

func (i ImmobilizeState) String()(string){
	switch i{
	case STATE_IMMOBILIZED:
		return "STATE_IMMOBILIZED"
	case STATE_IMMOBILIZING:
		return "STATE_IMMOBILIZING"
	case STATE_MOBILIZING:
		return "STATE_MOBILIZING"
	case STATE_MOBILIZED:
		return "STATE_MOBILIZED"
	}
	return "missing"
}

type RedisImmobilizerMsg struct {
	VIN   string          // VIN that we are modifying
	State ImmobilizeState // How we are changing said state, which will affect how listening ditto's will manage
}

func (rim RedisImmobilizerMsg) MarshalBinary() ([]byte, error) {
	return json.Marshal(rim)
}

func (rim RedisImmobilizerMsg) UnmarshalBinary(data []byte) error {
	return json.Unmarshal(data, &rim)
}
