package background

import (
	"encoding/json"
	"maps"
	"otaupdate/services"
	"sync"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/cache"
	"github.com/fiskerinc/cloud-services/pkg/carcommand"
	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/redis"
)

// Anywhere a comment says lock, its probably actually lock/unlock

// Have a local list we check and run against
// and have a redis cache list that we will modify
// keep track of time started, last updated time, number of times sent, and if its a lock or unlock

var (
	immobilizerOnce sync.Once
	immobilizer     *Immobilizer
)

// Message will stay alive in redis for an hour before it is removed
const CAR_CHECK_INTERVAL time.Duration = time.Duration(5 * time.Minute)      // Minutes between checking car status and sending command
const REDIS_UPDATE_INTERVAL time.Duration = time.Duration(time.Hour * 2)      // Minutes between updating redis with the current cache list. // need to keep track of last updated incase our service goes down
const REDIS_EXPIRATION_DURATION time.Duration = time.Duration(24 * time.Hour) // how old a time should be before we pick it up for us to process

const CAR_LOCK_ATTEMPT_COUNT = 15 // how many times to send the lock command

type CarTrack struct {
	TimesModified int  `json:"times_modified"`
	Immobilize    bool `json:"immobilize"` // 0: unlock, 1: lock
}

type Immobilizer struct {
	VINs         map[string]*CarTrack // By having a pointer here, should be able to modify timesModified without write lock
	sync.RWMutex                      // mutex for handling the reading and writing of the VIN's map
}

func GetImmobilizer() *Immobilizer {
	immobilizerOnce.Do(func() {
		if immobilizer != nil {
			return
		}
		logger.Info().Msg("Init Immobilizer instance")
		immobilizer = InitiateImmobilizer()
	})
	return immobilizer
}

func InitiateImmobilizer() *Immobilizer {
	imm := &Immobilizer{}
	imm.VINs = make(map[string]*CarTrack)
	go time.AfterFunc(CAR_CHECK_INTERVAL, imm.CheckCars)
	// go time.AfterFunc(REDIS_UPDATE_INTERVAL, imm.UpdateRedis)
	// Have a random offset so multiple ota's starting at same time don't look and claim at the same time
	// go time.AfterFunc(REDIS_EXPIRATION_DURATION+(time.Duration(rand.IntN(20))*time.Minute), imm.ClaimRedis)
	return imm
}


func (imm *Immobilizer) GetVINList() (vinList []string) {
	imm.RLock()
	defer imm.RUnlock()
	for vin := range imm.VINs {
		vinList = append(vinList, vin)
	}
	return
}

func (imm *Immobilizer) CheckCars() {
	clientPool := services.RedisClientPool()
	twins, _ := cache.GetVINListDigitalTwin(imm.GetVINList(), clientPool)
	parkedVINs := []string{} // parkedVINs are the cars we are going to send the lock command to
	for vin, twin := range twins {
		gear := twin.Gear
		if gear == nil {
			continue
		}
		if gear.InPark {
			parkedVINs = append(parkedVINs, vin)
		}
	}
	// Have a list of vins we can now modify
	// Send the lock/unlock command, send wake up command
	hopefulImmob := imm.sendRemoteCommands(parkedVINs)
	// Not on hopeful immob, we do not check if the car ever wakes up from its sms, so its possible if parked deep in a garage it will not
	// Possible fix: remove redis timeout for car lock commands
	go imm.RemoveVINs(hopefulImmob)
	time.AfterFunc(CAR_CHECK_INTERVAL, imm.CheckCars)
}

func (imm *Immobilizer) sendRemoteCommands(parkedVINs []string) (removableVins []string) {
	// First send wake up message

	// Send lock or unlock command

	// 	for _, vin := range request.VINs {
	// Action logger should get added to when the user adds car to the list
	// 		go func() {
	// 			actionLog := actionlogger.ActionLog{
	// 				VIN:            vin,
	// 				Action:         actionlogger.RemoteCommand,
	// 				UserIdentifier: httphandlers.GetClientID(r),
	// 				CallLocation:   "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/vehicle_command.go",
	// 				Description:    string(description),
	// 			}
	// 			err = alDB.Insert(actionLog)
	// 			if err != nil {
	// 				logger.Err(err).Msg("failed to insert action log inside HandleVehicleCommand")
	// 			}
	// 		}()
	// vehicle_command.go has an extremely convoluted way to get remote commands to car. It pushed it to a kafka queue to to be picked up
	// then kafka does some delivery re-trying stuff in some way, wakes up the car and then puts the message into redis.
	// we are going straight to the redis section

	// remoteCommands := make([]string, 0, len(parkedVINs))
	imm.RLock()
	batch := redis.NewRedisBatchCommands()
	smsClient := services.GetSMSClient()
	wake := carcommand.NewCarWakeUp(services.GetDB().GetCars(), smsClient)
	for _, vin := range parkedVINs {
		temp := imm.VINs[vin]
		temp.TimesModified -= 1
		if temp.TimesModified < 0 {
			removableVins = append(removableVins, vin)
			continue
		}
		// probably faster to create the list of things to push and batch, but fine for now
		msg := common.RemoteCommandSource{}
		if temp.Immobilize {
			msg.Command = "doors_lock"
		} else {
			msg.Command = "doors_unlock"
		}
		// SafeQueueMessage auto deletes after one hour, which I do not want with this lock command, rather it sat around indefinitely
		data, _ := json.Marshal(common.Message{
			Handler: "remote_command",
			Data:    msg,
		})
		batch.Add("RPUSH", redis.QueueKey(common.TRex.Key(vin)), data)

		// try to wake up car
		wake.WakeUp(vin, false)
	}
	imm.RUnlock()

	redisClient := services.RedisClientPool().GetFromPool()
	defer redisClient.Close()
	_, err := redisClient.ExecuteBatch(batch)
	if err != nil {
		logger.Err(err).Msg("failed to push car immobilizer commands to redis")
	}
	return
}

func (imm *Immobilizer) RemoveVINs(vins []string) {
	imm.Lock()
	defer imm.Unlock()
	for _, v := range vins {
		delete(imm.VINs, v)
	}
}

func (imm *Immobilizer) AddVINs(vins []string, immobilize bool) {
	imm.Lock()
	defer imm.Unlock()
	for _, v := range vins {
		imm.VINs[v] = &CarTrack{TimesModified: CAR_LOCK_ATTEMPT_COUNT, Immobilize: immobilize}
	}
}

// Just returns the information about the vins we are currently tracking
// not sure about memory safety on this
func (imm *Immobilizer) GetVINInformation() (vinInfo map[string]*CarTrack) {
	imm.RLock()
	defer imm.RUnlock()
	vinInfo = maps.Clone(imm.VINs)
	return
}

func (imm *Immobilizer) UpdateRedis() {

}

func (imm *Immobilizer) ClaimRedis() {

}
