package immobilizerditto

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"sync"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/dbc/state"
	"github.com/fiskerinc/cloud-services/pkg/immobilizer/immobilizershared"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/redisv2"
)

// I should put these parked values as a constant somewhere TODO

func (id *ImmobilizeDitto) VCUGearSig(vin string, newState int) {
	//0 "Undefined_initial_value" 1 "gear_P" 2 "gear_N" 3 "R_gear" 4 "D_gear" 5 "Reserved" 6 "gear_E" 7 "gear_S" 8 "Reserved" 9 "Reserved" 10 "Reserved" 11 "Reserved" 12 "Reserved" 13 "Reserved" 14 "Reserved" 15 "Reserved" ;
	id.WatchListSync.RLock()
	track, ok := id.WatchList[vin]
	id.WatchListSync.RUnlock()
	if !ok {
		return
	}

	switch track.State {
	case immobilizershared.STATE_IMMOBILIZED:
		// If we are immobilized, but we are out of park, thats an issue
		if newState == 1 || newState == 2 {
			// Car is parked, and possible already immobilized, but we are going to enforce in case we were wrong
			// id.sendLockCommand(vin)
		} else if newState > 2 {
			logger.Error().Str("VIN", vin).Int("New Gear", newState).Str("MSG", "Marked as Immobilized, but received driving gear").Msg("VCUGearSig")
			go alertAmericanLeaseMovingVehicle(vin)
		}
	case immobilizershared.STATE_IMMOBILIZING:
		if newState == 1 || newState == 2 {
			// Car is parked, send that lock command. We know the car is awake, so no need to send a wake up sms
			id.sendLockCommand(vin)
		}
	case immobilizershared.STATE_MOBILIZING:
		if newState > 2 {
			// unsure if we should accept this as done
			immobilizershared.UpdateCarMobilized(vin, id.RedisClient)
		}
	}
}

func (id *ImmobilizeDitto) sendLockCommand(vin string) {
	msg := common.RemoteCommandSource{}
	msg.Command = "doors_lock"

	// 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,
	})
	res := id.RedisClient.RPush(context.Background(), redisv2.QueueKey(common.TRex.Key(vin)), data)
	err := res.Err()
	if err != nil {
		logger.Err(err).Msg("Failed to send lock command")
	}
}

func alertAmericanLeaseMovingVehicle(vin string) {
	url := "https://mobile.americanlease.net/api/v1/fisker/command/failure"
	token := "Bearer f3d795d3-5325-42d7-8dd8-ccb416c52ae2"

	type AmericanLeaseBody struct {
		VIN         string    `json:"vin"`
		UTCMobileAt time.Time `json:"utcMobileAt"` //"utcMobileAt":"2025-03-07T09:00:00",
		UUID        string    `json:"uuid"`        //"uuid": "db6e407d-791b-4d00-a742-da856a42c4ba"
	}

	body := AmericanLeaseBody{
		VIN:         vin,
		UTCMobileAt: time.Now().UTC(),
		UUID:        "00000000-0000-0000-0000-000000000000",
	}

	b, _ := json.Marshal(body)

	// Create the HTTP request
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
	if err != nil {
		logger.Err(err).Msg("failed to make request to american lease")
		return
	}

	// Set headers
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", token)

	// Execute the request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		logger.Err(err).Msg("failed to do request to american lease")
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 300 {
		respBody, _ := io.ReadAll(resp.Body)
		logger.Error().Str("response body", string(respBody)).Str("response header", resp.Status).Msg("failed to contact american lease about moving car")
	}
}

// So we got the VCU Immo sts, most likely a new value compared to what was had before
func (id *ImmobilizeDitto) VCUIMMOSts(vin string, newState string) {
	// Possible values for newState
	// "Immo_Active",
	// "Immo_Inactive",
	// "Reserved",
	// "Invalid",
	// Check if we are tracking this vehicle
	id.WatchListSync.RLock()
	track, ok := id.WatchList[vin]
	id.WatchListSync.RUnlock()
	if !ok {
		return
	}

	logger.Debug().Str("New State", newState).Str("Tracked", track.State.String()).Str("VIN", vin).Msg("VCUIMMOSts signal")
	switch track.State {
	case immobilizershared.STATE_IMMOBILIZED:
		// Car is currently marked as immobilized
		if newState == "Immo_Inactive" {
			// This is an issue, we have the car as being immobilized, but it just got moving. Now this could possible be a timing issue between a different ditto
			// If this actually happens, we need to set the vehicle to Immobilizing
			logger.Error().Str("VIN", vin).Str("MSG", "Marked as Immobilized, but received Immo_Inactive").Msg("Ditto Immobilizer")
		}
		// if newState == "Immo_Active" {
		// 	// This is can be ignored
		// }
	case immobilizershared.STATE_IMMOBILIZING:
		// We are trying to immobilize the vehicle
		if newState == "Immo_Active" {
			immobilizershared.UpdateCarImmobilized(vin, id.RedisClient)
			// Send lock command some more
			id.sendLockCommand(vin)
		}
	case immobilizershared.STATE_MOBILIZING:
		// Hey great, we know that the car is now active
		if newState == "Immo_Inactive" {
			immobilizershared.UpdateCarMobilized(vin, id.RedisClient)
		}
	}
}

func (id *ImmobilizeDitto) PKC_KeyStsMod(vin string, newState string) {
	id.WatchListSync.RLock()
	track, ok := id.WatchList[vin]
	id.WatchListSync.RUnlock()
	if !ok {
		return
	}

	logger.Debug().Str("New State", newState).Str("Tracked", track.State.String()).Str("VIN", vin).Msg("PKC_KeyStsMode signal")
	switch track.State {
	case immobilizershared.STATE_IMMOBILIZED:
		// Car is currently marked as immobilized
		if newState == state.PKC_KeyStsMod_disabled {
			// we thought the car was immobilized, but we just got a signal saying its been mobilized
			logger.Error().Str("VIN", vin).Str("MSG", "Marked as Immobilized, but received PKC_KeyStsMod: disabled").Msg("Ditto Immobilizer")
		}
	case immobilizershared.STATE_IMMOBILIZING:
		// We were trying to immobilize, and we got confirmation that it is
		if newState == state.PKC_KeyStsMod_enabled {
			immobilizershared.UpdateCarImmobilized(vin, id.RedisClient)
		}
	case immobilizershared.STATE_MOBILIZING:
		// trying to get car moving, and got confirmation it is
		if newState == state.PKC_KeyStsMod_disabled {
			immobilizershared.UpdateCarMobilized(vin, id.RedisClient)
		}
	}
}

// Ongoing process, needs to be run in da background
func (id *ImmobilizeDitto) ListenToRedisChanges() {
	subscription := id.RedisClient.Subscribe(context.Background(), immobilizershared.IMMOBILIZER_PUBSUB_CHANNEL)
	subChannel := subscription.Channel()
	for msg := range subChannel {
		rim := immobilizershared.RedisImmobilizerMsg{}
		err := json.Unmarshal([]byte(msg.Payload), &rim)
		if err != nil {
			logger.Err(err).Str("Payload", msg.Payload).Msg("ListenToRedisChanges: Failed to parse payload")
			continue
		}
		id.WatchListSync.Lock()
		if rim.State == immobilizershared.STATE_MOBILIZED {
			delete(id.WatchList, rim.VIN)
		} else {
			id.WatchList[rim.VIN] = immobilizershared.ImmobilizeTrack{State: rim.State}
		}
		id.WatchListSync.Unlock()
	}
}

// Read in redis to get out initial state
func (id *ImmobilizeDitto) SeedLocalData() {
	res, err := immobilizershared.ReadVehicleStatuses(id.RedisClient)
	if err != nil {
		return
	}
	id.WatchListSync.Lock()
	id.WatchList = res
	id.WatchListSync.Unlock()
}

// Don't want to become de-synced, so will seed once an hour
func (id *ImmobilizeDitto) SeedHourly() {
	id.SeedLocalData()
	time.AfterFunc(time.Hour, id.SeedHourly)
}

func InitImmobilizerDitto(connection *redisv2.Connection) (id *ImmobilizeDitto) {
	id = &ImmobilizeDitto{}
	id.RedisClient = connection
	go id.SeedHourly()
	go id.ListenToRedisChanges()
	return id
}

// Wether a car is to start mobilizing or immobilizing is a decision by the server, not from a cars messaging
type ImmobilizeDitto struct {
	RedisClient   *redisv2.Connection
	WatchList     map[string]immobilizershared.ImmobilizeTrack
	WatchListSync sync.RWMutex
}
