package services

import (
	"fmt"
	"reflect"
	"sync"
	"time"

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

// This can probably be moved into a better spot, but we are on a time limit
var ADD_TIME = time.Minute

type KeepAwake struct {
	tick *time.Ticker
	KAI  KeepAwakeInterface
}

type KeepAwakeInterface interface {
	CheckKeepAwakeMessages()
	// addToKeepAwakeMessages(vin string) (err error)
	RemoveKeepAwakeMessage(vin string) (err error)
	// getKeepAwakeMessages() (vins []string, err error)
	SendFirstKeepAwakeMessage(vin string) (err error)
	// sendKeepAwakeMessage(vin string) (err error)
}

type KeepAwakeImplementation struct {
	Cars    map[string]time.Time // Map of car vins to the time it was last ran
	oneTime sync.Once
	mapLock sync.Mutex
}

func NewKeepAwakeService() (ka *KeepAwake) {
	ka = &KeepAwake{}

	ka.tick = time.NewTicker(time.Minute)
	ka.KAI = &KeepAwakeImplementation{}

	// Start our timer
	go func() {
		for {
			select {
			case <-ka.tick.C:
				ka.KAI.CheckKeepAwakeMessages()
			}
		}
	}()
	return
}

func (ka *KeepAwake) SetService(inf KeepAwakeInterface) {
	ka.KAI = inf
}

func (ka *KeepAwake) RemoveKeepAwakeMessage(vin string) (err error) {
	return ka.KAI.RemoveKeepAwakeMessage(vin)
}

func (ka *KeepAwake) SendFirstKeepAwakeMessage(vin string) (err error) {
	return ka.KAI.SendFirstKeepAwakeMessage(vin)
}

// Need to move this out of here, as this manifest sender is created every time a manifest is to be sent
// On a timeout, check if the keep awake messages need to be sent
func (k *KeepAwakeImplementation) CheckKeepAwakeMessages() {
	k.oneTime.Do(func() {
		k.Cars = make(map[string]time.Time)
	})

	k.mapLock.Lock()
	vins, _ := k.getKeepAwakeMessages()

	for _, vin := range vins {
		k.sendKeepAwakeMessage(vin)

		k.addToKeepAwakeMessages(vin)
	}
	k.mapLock.Unlock()
	//  Call this function again in some amount of time
}

func (k *KeepAwakeImplementation) addToKeepAwakeMessages(vin string) (err error) {
	k.Cars[vin] = time.Now().Add(ADD_TIME)
	return
}

func (k *KeepAwakeImplementation) RemoveKeepAwakeMessage(vin string) (err error) {
	k.oneTime.Do(func() {
		k.Cars = make(map[string]time.Time)
	})

	// Remove from Redis
	err = k.removeRedisCarEntry(vin)
	if err != nil {
		logger.Err(err).Msg("Unable to remove keeep awake message from redis")
	}

	logger.Info().Msgf("Ended keep awake messaging for %s", vin)
	k.mapLock.Lock()
	defer k.mapLock.Unlock()
	_, ok := k.Cars[vin]
	if !ok {
		logger.Debug().Msgf("Attempted to end keep awake for %s, but not in list", vin)
	}
	delete(k.Cars, vin)

	return err
}

// This is a list of vins that need sendKeepAwakeMessage
func (k *KeepAwakeImplementation) getKeepAwakeMessages() (vins []string, err error) {
	for vin, t := range k.Cars {
		// Parse out the time and check if its before the time it is now
		if t.Before(time.Now()) {
			// This needs processing
			vins = append(vins, vin)
		}
	}
	return
}

func (k *KeepAwakeImplementation) SendFirstKeepAwakeMessage(vin string) (err error) {
	k.oneTime.Do(func() {
		k.Cars = make(map[string]time.Time)
	})

	// Additionally add to the list of redis
	err = k.addRedisCarEntry(vin)
	logger.Err(err).Msg("")

	logger.Info().Msgf("Started keep awake messaging for %s", vin)
	k.mapLock.Lock()
	defer k.mapLock.Unlock()
	err = k.sendKeepAwakeMessage(vin)
	k.addToKeepAwakeMessages(vin)
	return err
}

// Send a special message to keep the tbox awake to download the update
// We want to continue to call this until the download is complete or if we fail
func (k *KeepAwakeImplementation) sendKeepAwakeMessage(vin string) (err error) {
	client := RedisClientPool().GetFromPool()
	defer client.Close()

	// check if in redis
	found, err := k.checkRedisForCarEntry(vin, client)
	if err != nil {
		return
	}
	// If we did not find the redis entry, then another service has removed it from the keep alive set
	// Don't call k.Delete as it creates a lock on the map, and then we will be deadlocked
	if !found {
		delete(k.Cars, vin)
		return
	}

	logger.Info().Msgf("sending keep awake message for %s", vin)

	type Action struct {
		Action  string `json:"action"`
		Timeout int32  `json:"timeout"`
	}
	logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "attendant", vin, "can_network", Action{Action: "on"})
	err = client.SafeQueueMessage(common.TRex.Key(vin), common.Message{
		Handler: "can_network",
		Data:    Action{Action: "on", Timeout: 120},
	})

	return errors.WithStack(err)
}

func (k *KeepAwakeImplementation) checkRedisForCarEntry(vin string, client redis.Client) (found bool, err error) {
	line, err := client.Execute("SISMEMBER", "can_keep_awake", vin)
	if err != nil {
		return false, errors.WithStack(err)
	}

	check, ok := line.(int64)
	if !ok {
		err = fmt.Errorf("received wrong type from redis for the can_keep_awake for %s, %v, %s", vin, line, reflect.TypeOf(line))
	}
	return check > 0, err
}

func (k *KeepAwakeImplementation) removeRedisCarEntry(vin string) (err error) {
	client := RedisClientPool().GetFromPool()
	defer client.Close()
	_, err = client.Execute("SREM", "can_keep_awake", vin)
	return
}

func (k *KeepAwakeImplementation) addRedisCarEntry(vin string) (err error) {
	client := RedisClientPool().GetFromPool()
	defer client.Close()

	_, err = client.Execute("SADD", "can_keep_awake", vin)
	return
}

type MockKeepAwakeImplementation struct{}

func (k *MockKeepAwakeImplementation) CheckKeepAwakeMessages() {}

func (k *MockKeepAwakeImplementation) RemoveKeepAwakeMessage(vin string) (err error) {
	return err
}

func (k *MockKeepAwakeImplementation) SendFirstKeepAwakeMessage(vin string) (err error) {
	return
}
