package digitaltwin

import (
	"encoding/json"

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

func NewSendDigitalTwin(redis redis.ClientPoolInterface, cars queries.CarsInterface) SendDigitalTwin {
	return SendDigitalTwin{
		redisClientPool: redis,
		carsDB:          cars,
	}
}

type SendDigitalTwin struct {
	redisClientPool redis.ClientPoolInterface
	carsDB          queries.CarsInterface
	parser          *cache.VehicleState
}

func (d *SendDigitalTwin) Close() {
	d.carsDB = nil
}

func (d *SendDigitalTwin) GetDigitalTwin(vin string) (*common.JSONDigitalTwin, error) {
	state, err := d.getVehicleState(vin)
	if err != nil {
		return nil, err
	}

	return CreateDigitalTwinResponse(vin, state), nil
}

func CreateDigitalTwinResponse(vin string, state common.CarState) *common.JSONDigitalTwin {
	var battery *common.JSONBattery
	var windows *common.JSONWindows
	var climateControl *common.JSONClimateControl

	battery = consolidateBattery(state)

	// Consolidate windows
	if state.Windows != nil && state.MiscWindows != nil && state.Sunroof != nil {
		windows = &common.JSONWindows{}

		// Main windows
		windows.LeftFront = state.Windows.LeftFront
		windows.LeftRear = state.Windows.LeftRear
		windows.RightFront = state.Windows.RightFront
		windows.RightRear = state.Windows.RightRear

		// Miscellaneous windows
		windows.LeftRearQuarter = state.MiscWindows.LeftRearQuarter
		windows.RightRearQuarter = state.MiscWindows.RightRearQuarter
		windows.RearWindshield = state.MiscWindows.RearWindshield

		// Sunroof
		windows.Sunroof = state.Sunroof.Sunroof
	}

	// Consolidate climate control
	if state.CabinClimate != nil && state.RearDefrost != nil && state.DriverSeatHeat != nil &&
		state.PassengerSeatHeat != nil && state.SteeringWheelHeat != nil && state.AmbientTemperature != nil {
		climateControl = &common.JSONClimateControl{}

		climateControl.CabinTemperature = state.CabinClimate.CabinTemperature
		climateControl.RearDefrost = state.RearDefrost.On
		climateControl.DriverSeatHeat = state.DriverSeatHeat.Level
		climateControl.PassengerSeatHeat = state.PassengerSeatHeat.Level
		climateControl.SteeringWheelHeat = state.SteeringWheelHeat.On
		climateControl.AmbientTemperature = state.AmbientTemperature.Temperature
		climateControl.InternalTemperature = state.CabinClimate.InternalTemperature
	}

	twinForDrivers := &common.JSONDigitalTwin{
		VIN:               vin,
		Online:            state.Online,
		OnlineHMI:         state.OnlineHMI,
		VehicleSpeed:      state.VehicleSpeed,
		Gear:              state.Gear,
		Battery:           battery,
		Doors:             state.Doors,
		Location:          state.Location,
		Locks:             state.Locks,
		Windows:           windows,
		ClimateControl:    climateControl,
		TRexVersion:       state.TRexVersion,
		IP:                state.IP,
		VehicleReadyState: state.VehicleReadyState,
		ExpandedSignals:   state.ExpandedSignals,
		UpdatedAt:         state.UpdatedAt,
	}

	return twinForDrivers
}

func consolidateBattery(state common.CarState) *common.JSONBattery {
	battery := common.JSONBattery{}
	if state.StateOfCharge != nil {
		battery.StateOfCharge = &state.StateOfCharge.Usable
	}
	if state.Battery != nil {
		battery.Percent = &state.Battery.Percent
		battery.TotalMileageOdometer = &state.Battery.TotalMileageOdometer
	}
	if state.MaxRange != nil {
		battery.MaxMiles = &state.MaxRange.MaxMiles
	}
	if state.VCU0x260 != nil {
		battery.ChargeType = &state.VCU0x260.ChargeType
	}
	if state.ChargingMetrics != nil {
		battery.RemainingChargingTime = &state.ChargingMetrics.RemainingChargingTime
	}
	if state.ChargingMetrics != nil {
		battery.RemainingChargingTimeFull = &state.ChargingMetrics.RemainingChargingTimeFull
	}
	if state.CellTemperature != nil {
		battery.AvgCellTemperature = &state.CellTemperature.AvgBatteryTemp
	}

	if battery == (common.JSONBattery{}) {
		return nil
	}

	return &battery
}

func (d *SendDigitalTwin) sendToDriver(twin *common.JSONDigitalTwin, driver string) error {
	dt, err := json.Marshal(twin)
	if err != nil {
		logger.Error().Err(errors.WithStack(err)).Send()
	}
	sdt := string(dt)
	logger.Debug().Msg(sdt)

	logger.Info().Interface("digital twin", twin).Msg("send to driver add to redis")
	client := d.redisClientPool.GetFromPool()
	defer client.Close()
	err = client.SafePublishMessage(
		common.Mobile.Key(driver),
		common.Message{
			Handler: "digital_twin",
			Data:    twin,
		},
	)
	return err
}

func (d *SendDigitalTwin) SendToDriver(vin string, driver string) error {
	ok, err := d.verifyCarToDriver(vin, driver)
	if err != nil {
		return err
	} else if !ok {
		return cache.ErrInvalidCarToDriverAssociation(vin, driver)
	}

	twin, err := d.GetDigitalTwin(vin)
	if err != nil {
		return err
	}

	return d.sendToDriver(twin, driver)
}

func (d *SendDigitalTwin) Send(vin string) error {
	// Get the digital twin
	redisDigitalTwin, err := d.GetDigitalTwin(vin)
	if err != nil {
		return err
	}
	drivers, err := d.retrieveDriverIDs(vin)
	if err != nil {
		return err
	}
	if len(drivers) == 0 {
		return nil
	}

	if redisDigitalTwin != nil {
		for _, driver := range drivers {

			err = d.sendToDriver(redisDigitalTwin, driver)
			if err != nil {
				logger.Error().Err(err)
			}
		}
	}

	return nil
}

func (d *SendDigitalTwin) getVehicleState(vin string) (common.CarState, error) {
	return d.getParser().Get(vin)
}

func (d *SendDigitalTwin) verifyCarToDriver(vin string, driver string) (bool, error) {
	return cache.VerifyCarToDriver(d.redisClientPool, d.carsDB, vin, driver)
}

func (d *SendDigitalTwin) retrieveDriverIDs(vin string) ([]string, error) {
	drivers := cache.NewDriversCache(d.redisClientPool, d.carsDB)

	return drivers.RetrieveDriverIDs(vin)
}

func (d *SendDigitalTwin) getParser() *cache.VehicleState {
	if d.parser == nil {
		d.parser = cache.NewVehicleState(d.redisClientPool)
	}

	return d.parser
}
