package manifestsender

import (
	"errors"
	"fmt"

	"github.com/fiskerinc/cloud-services/pkg/carcommand"
	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/common/manifestfingerprintparams"
	"github.com/fiskerinc/cloud-services/pkg/db/queries"
	q "github.com/fiskerinc/cloud-services/pkg/db/queries"
	"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/redis"
	uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers"
	"github.com/fiskerinc/cloud-services/pkg/validator"
	vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
	errorspkg "github.com/pkg/errors"
)

var FailedToGetCDS = errors.New("did not send tbox as we had failed to get cds")

type ManifestSenderDB interface {
	GetUpdateManifests() q.UpdateManifestsInterface
	GetCars() q.CarsInterface
	GetCarUpdates() q.CarUpdatesInterface
}

func NewTBOXManifestSender(
	r redis.Client,
	conf vconfig.ConfigServiceInterface,
	db ManifestSenderDB,
	sms sms.SMSServiceClient,
	cds map[string]string,
) *TBOXManifestSender {
	t := &TBOXManifestSender{
		Redis: r,
		db:    db,
		conf:  conf,
		sms:   sms,
		cds:   cds,
	}

	return t
}

type TBOXManifestSender struct {
	Redis redis.Client
	db    ManifestSenderDB
	conf  vconfig.ConfigServiceInterface
	sms   sms.SMSServiceClient
	cds   map[string]string // I think this can/should be removed. Unused in config route
}

func (t *TBOXManifestSender) Close() {
	t.Redis = nil
}

func (t *TBOXManifestSender) addRollback(manifest *common.UpdateManifest, vin string) error {
	if !manifest.RollbackEnabled {
		return nil
	}

	var err error

	db := t.db.GetUpdateManifests()
	for _, ecu := range manifest.ECUs {
		var rollbacks []*common.UpdateManifestECU
		rollbacks, err = db.ECURollback(ecu, vin)
		if err != nil {
			return errorspkg.WithStack(err)
		}

		ecu.Rollback = rollbacks
	}

	manifest.RemoveOriginalS19HexFilesRollbacks()

	return nil
}

// Prepare a manifest, gets cds data if need be
// This is called in two different locations. One form send_manifest.go and another from car_update_progress.go
// send_manifest.go includes all the fields one could want, the car_update_progress is not
func (t *TBOXManifestSender) ProcessSoftwareUpdate(vin string, manifest *common.UpdateManifest, ccd queries.CarConfigDataInterface) (smsID string, err error) {
	// Add rollbacks to manifest
	err = t.addRollback(manifest, vin)
	if err != nil {
		return
	}

	// No CDS means some tasks in the send_manifest has not been run
	// like VOD, CDS, ECU sorting, removing original s19 hex files, and current versions
	if t.cds == nil {
		var cds map[string]string
		cds, err = t.getVODCDS(vin, ccd)
		if err != nil {
			return
		}

		t.cds = cds
		manifest.SortECUs()
		manifest.RemoveOriginalS19HexFiles()
		manifest.RemoveOriginalS19HexFilesRollbacks()

		err = uhelpers.PopulateECUsCurrentVersion(t.db.GetCars(), vin, manifest.ECUs)
		if err != nil {
			return
		}
	}

	// Definitely need to transform the ECU's before the CDS are added so the names all match
	manifest.TransformECUNames()
	manifest.AddCDSToECUs(t.cds)
	manifest.FilterCompatibleECUs(vin)

	err = t.checkSUMS(manifest)
	if err != nil {
		return
	}

	fpparams := manifestfingerprintparams.GetFPParams()
	manifest.GenerateFingerprint(fpparams.CurTime(), fpparams.ManifestSerial())
	manifest.Scrub(common.TRex)

	return t.Send(vin, *manifest)
}

func (t *TBOXManifestSender) checkSUMS(manifest *common.UpdateManifest) error {
	if manifest.ManifestType == common.ConfigUpdateType {
		return nil
	}

	err := validator.ValidateField(manifest.SUMS, "required,sums_version")
	if err == nil {
		err = manifest.AddSUMSToVOD()
	}

	return err
}

// Send the update_manifest out redis to Trex, assume the manifest is all ready to go
// If we try to send a wakeup sms and the car does not have an ICCID we do not fail, as hopefully it it a test trex
func (t *TBOXManifestSender) Send(vin string, manifest common.UpdateManifest) (smsID string, err error) {
	logger.Debug().Msgf("Sending redis queue- %s, key- %s, hander- %s, data- %v", "manifestsender", vin, "update_manifest", manifest)
	err = t.Redis.SafeQueueMessage(common.TRex.Key(vin), common.Message{
		Handler: "update_manifest",
		Data:    manifest,
	})
	if err != nil {
		return "", err
	}

	// Here need to wake it up again
	logger.At(logger.Info(), common.TRex.Key(vin), "update").Msgf("TBOXManifestSender sent %v %s %d", common.TRex, vin, manifest.CarUpdateID)
	if manifest.Type == common.ManifestTypeForced {
		var queueResponse *sms.SMSQueueResponse
		queueResponse, err = carcommand.QueueSMSWakeUp(vin, true, t.Redis, t.db.GetCars(), t.sms)
		if err != nil {
			if errors.Is(err, carcommand.ErrNoICCIDForWakeUp) {
				err = nil
				queueResponse.SentSuccessful = true
			} else {
				return "", err
			}
		}

		if queueResponse.SentSuccessful {
			smsID = queueResponse.SmsMsgID
		} else {
			err = fmt.Errorf("failed to awake car for Send TBOXManifestSender for vin: %s, car update: %d", vin, manifest.CarUpdateID)
		}
	}

	return smsID, err
}

func (t *TBOXManifestSender) getVODCDS(vin string, db queries.CarConfigDataInterface) (map[string]string, error) {
	return uhelpers.GetCDS(t.conf, db, vin)
}
