package manifestsender

import (
	"fmt"
	"time"

	"errors"

	"github.com/fiskerinc/cloud-services/pkg/carcommand"
	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/common/carupdatestatus"
	"github.com/fiskerinc/cloud-services/pkg/db/queries"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils/whereami"
)

// ProcessConfigUpdate handles all steps on the config only update
// It will create a car_update record, manifest record, car_update_status record
type ProcessConfigUpdateStruct struct {
	VIN                     string // The vin of the car that configuration we are generating
	Username                string // The username of who is generating this update
	Name                    string // The name of the generated manifest, leave blank for auto generated. name,version is enforced unique
	SendToCar               bool   // True to send the manifest to the car
	DontCreateDatabaseEntry bool   // False to create database entry, by default. Cannot be true with SendToCar being true
	Forced                  bool
}

var ERR_SEND_AND_DATABASE_MISMATCH = errors.New("cannot send to car with dont create database entry")

// If called from aftersales, this will be marked as an aftersales update, which are not sent to the car
// and they are automatically cancelled when they are hit cancel on the portal
func (t *TBOXManifestSender) ProcessConfigUpdate(input ProcessConfigUpdateStruct, ccd queries.CarConfigDataInterface) (ucm common.UpdateConfigManifest, err error) {
	if input.DontCreateDatabaseEntry && input.SendToCar {
		return ucm, ERR_SEND_AND_DATABASE_MISMATCH
	}

	// We have the send to car, so that should be all the info we need to make changes based on the system :()()
	// Get the VOD and CDSs for the car
	cds, err := t.getVODCDS(input.VIN, ccd)
	if err != nil {
		return
	}

	manifest := common.UpdateManifest{}
	if input.Name != "" {
		manifest.Name = input.Name
	} else {
		manifest.Name = fmt.Sprintf("configuration update %s %s", input.VIN, time.Now().Format("2006-01-02"))
	}

	manifest.Version = time.Now().Format("2006-01-02 15:04:05")
	manifest.Description = fmt.Sprintf("configuration for %s by %s", input.VIN, input.Username)
	manifest.ManifestType = common.ConfigUpdateType

	if input.Forced {
		manifest.Type = common.ManifestTypeForced
	} else {
		manifest.Type = common.ManifestTypeStandard
	}

	manifestDB := t.db.GetUpdateManifests()
	if !input.DontCreateDatabaseEntry {
		_, err = manifestDB.Insert(&manifest)
		if err != nil {
			return
		}
	}

	// AddCDSToECUs calls AddVOD
	// AddECUsFromCDSList requires the manifest having been inserted so that they
	// have the manifestID
	manifest.AddECUsFromCDSList(cds)
	manifest.SortECUs()
	manifest.FillECUList()

	//  Create a car update for this manifest ot be sent as
	updatesDB := t.db.GetCarUpdates()
	update := common.CarUpdate{
		VIN:              input.VIN,
		UpdateManifestID: manifest.ID,
		UpdateManifest:   &manifest,
		Username:         input.Username,
		Status:           carupdatestatus.Pending,
	}

	if whereami.Service == whereami.AFTERSALES {
		input.SendToCar = false
		update.UpdateSource = common.UPDATE_SOURCE_AFTERSALES
	} else {
		update.UpdateSource = common.UPDATE_SOURCE_OTA
	}

	err = insertUpdateDatabase(input, updatesDB, manifestDB, manifest, &update)
	if err != nil {
		return
	}

	manifest.CarUpdateID = update.ID

	manifest.Scrub(common.TRex)
	ucm = manifest.ToUpdateConfigManifest()

	if !input.SendToCar {
		return
	}
	return ucm, t.SendConfig(input.VIN, ucm)
}

func insertUpdateDatabase(input ProcessConfigUpdateStruct, updatesDB queries.CarUpdatesInterface, manifestDB queries.UpdateManifestsInterface, manifest common.UpdateManifest, update *common.CarUpdate) (err error) {
	if !input.DontCreateDatabaseEntry {
		_, err = updatesDB.InsertAndCreateStatus(update)
		if err != nil {
			_, err2 := manifestDB.Delete(&manifest)
			if err2 != nil {
				err = errors.Join(err, err2)
			}
			return
		}
	}
	return
}

func (t *TBOXManifestSender) SendConfig(vin string, manifest common.UpdateConfigManifest) (err error) {
	err = t.Redis.SafeQueueMessage(common.TRex.Key(vin), common.Message{
		Handler: "config_update",
		Data:    manifest,
	})
	if err != nil {
		return err
	}

	logger.At(logger.Info(), common.TRex.Key(vin), "update").Msgf("TBOXManifestSender sent %v %s %d", common.TRex, vin, manifest.CarUpdateID)

	// For now, do not check on config update if the car wakes up correctly
	if manifest.Type == common.ManifestTypeForced && t.sms != nil{
		_, err = carcommand.QueueSMSWakeUp(vin, false, t.Redis, t.db.GetCars(), t.sms)
		if err != nil {
			return err
		}
	}

	return nil
}
