package controllers

import (
	"encoding/json"
	"fmt"

	"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"

	"github.com/fiskerinc/cloud-services/services/attendant/services"

	"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/common/manifestfingerprintparams"
	"github.com/fiskerinc/cloud-services/pkg/grpc/sms"
	"github.com/fiskerinc/cloud-services/pkg/hwversion"
	"github.com/fiskerinc/cloud-services/pkg/kafka"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/manifestsender"
	"github.com/fiskerinc/cloud-services/pkg/redis"
	"github.com/fiskerinc/cloud-services/pkg/tmobile"
	uhelpers "github.com/fiskerinc/cloud-services/pkg/usecase_helpers"
	"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
	"github.com/fiskerinc/cloud-services/pkg/utils/randomvalues"
	"github.com/fiskerinc/cloud-services/pkg/utils/whereami"
	"github.com/fiskerinc/cloud-services/pkg/validator"
	vconfig "github.com/fiskerinc/cloud-services/pkg/vehicleconfig"
	"google.golang.org/protobuf/proto"

	"sync"
	"time"

	"github.com/pkg/errors"
)

// 0100084000000101012200010101010001010101000000000000000000ff7eff7f000101010101000101010100010001010101000101010100000000000000000000000000010101000100000100010101000201010101000101020101000101010200010101010101010101000101000100010001010101010101010101010000000000000100010101020101010101010000000000000000ffffff00010102010102020001010000000000000000000000000000000000000000000000000000000000000000000100202310010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6
var defaultVOD string = envtool.GetEnv("DEFAULT_VOD", "00FF111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111114A")

var fileKeyBypass map[int64]string
var FileKeyBypassManifest_US map[int64]string = map[int64]string{
	1189: "fe4f19fbc940ed58",
	1380: "6a44fd71c940716d",
}

var FileKeyBypassManifest_EU map[int64]string = map[int64]string{
	893: "fe4f19fbc940ed58",
	1002: "6a44fd71c940716d",
	1012: "d70e1d32c940a221",
}

func init() {
	if whereami.Environment == whereami.PRODUCTION_EU {
		fileKeyBypass = FileKeyBypassManifest_EU
	} else {
		fileKeyBypass = FileKeyBypassManifest_US
	}

}

func NewManifestSender(
	r redis.ClientPoolInterface,
	db *services.DB,
	conf vconfig.ConfigServiceInterface,
	device common.Device,
	ka *services.KeepAwake,
	seed int64, //This seed is used only for testing
) *ManifestSender {
	randomGenerator := randomvalues.NewNonCryptoGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ", seed)
	return &ManifestSender{
		Redis:  r,
		db:     db,
		conf:   conf,
		Device: device,
		sms:    services.GetSMSClient(),
		ka:     ka,
		gen:    randomGenerator,
	}
}

type ManifestSender struct {
	Redis  redis.ClientPoolInterface
	db     *services.DB
	conf   vconfig.ConfigServiceInterface
	sms    sms.SMSServiceClient
	Device common.Device
	ka     *services.KeepAwake
	gen    randomvalues.NonCryptoGenerator
}

func (m *ManifestSender) Process(id string, data []byte) error {
	// id string parameter is unused except in the following log
	// the data might not be used either, just for its single carUpdateID
	u, err := m.parseRequest(data)
	if err != nil {
		logger.Err(err).Msgf("Manifest sender, unable to process update %s", id)
		return err
	}

	carUpdate := common.CarUpdate{ID: u.CarUpdateID}
	err = m.loadCarUpdate(&carUpdate)
	if err != nil {
		return err
	}

	logger.Info().Msgf("ManifestSender car_update_id %d", carUpdate.ID)

	carUpdate.UpdateManifest.CarUpdateID = carUpdate.ID
	helper := uhelpers.NewECUKeys(m.db.GetECCKeys())
	err = helper.AddECUECCKeys(carUpdate.UpdateManifest)
	if err != nil {
		logger.Err(err).Msgf("Unable to decrypt keys for %d", carUpdate.ID)
		return err //comment out for local debugging
	}

	// If we fail to deliver the sms, then we don't believe the car has awoken
	// then set the manifest as failed
	err = m.send(carUpdate.VIN, carUpdate.UpdateManifest)
	if err != nil {
		update := NewCarUpdateProgress(m.Redis, m.ka, m.db, common.TRex)
		err = update.ProcessStatus(carUpdate.VIN, common.CarUpdateProgress{
			CarUpdateID: u.CarUpdateID,
			Status:      carupdatestatus.ManifestCanceled,
			Info:        " Underlying error: " + err.Error(),
		})
	}

	return err
}

func (m *ManifestSender) destination(manifest *common.UpdateManifest) string {
	if manifest.HasSelfDownload() {
		return "ICC"
	} else {
		return "ICC/TBOX"
	}
}

func (m *ManifestSender) parseRequest(data []byte) (*common.UpdateManifest, error) {
	var req common.UpdateManifest

	err := json.Unmarshal(data, &req)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	err = validator.ValidateIDField(req.CarUpdateID)
	if err != nil {
		return &req, errors.WithStack(err)
	}

	return &req, nil
}

func (m *ManifestSender) loadCarUpdate(cu *common.CarUpdate) error {
	err := m.db.GetCarUpdates().Load(cu)
	if err != nil {
		return errors.WithStack(err)
	}

	if cu.UpdateManifest == nil {
		return nil
	}

	return nil
}

func (m *ManifestSender) queueManifest(vin string, manifest *common.UpdateManifest) error {
	loggedManifest := manifest.Copy()
	loggedManifest.RemoveECCKeysFromECUs()
	logger.At(logger.Info(), common.HMI.Key(vin), "update").Interface("manifest.json", loggedManifest).Msgf("HMI ManifestSender sent %v %s %d", common.HMI, vin, manifest.CarUpdateID)
	client := m.Redis.GetFromPool()
	defer client.Close()
	err := client.SafeQueueMessage(common.HMI.Key(vin), common.Message{
		Handler: "update_manifest",
		Data:    manifest,
	})
	if err != nil {
		logger.Err(err).Msgf("failed to queue manifest in redis vin %s ", vin)
	}
	return err
}

func (m *ManifestSender) send(vin string, manifest *common.UpdateManifest) error {
	updateManifestID := manifest.ID
	manifest.TransformECUNames()
	err := hwversion.SetHWVersion(manifest, vin, m.db.GetCars())
	if err != nil {
		logger.Err(err).Msgf("manifest sender failed at SetHwVersion  for vin %s", vin)
		return err //comment out for local debugging
	}

	manifest.SortECUs()
	manifest.RemoveOriginalS19HexFiles()
	manifest.RemoveOriginalS19HexFilesRollbacks()

	manifest.FilterCompatibleECUs(vin)

	err = uhelpers.PopulateECUsCurrentVersion(m.db.GetCars(), vin, manifest.ECUs)
	if err != nil {
		logger.Err(err).Msgf("manifest sender failed at PupulateECUsCurrentVersion for vin %s", vin)
		return err
	}

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

	hmiManifest := manifest.Copy()

	cds, err := m.addVOD(hmiManifest, vin)
	if err != nil {
		logger.Err(err).Msgf("manifest sender failed at adding the vod for vin %s", vin)
		return err // comment out for local debugging
	}

	err = validator.ValidateField(hmiManifest.SUMS, "sums_version")
	if err == nil {
		err = hmiManifest.AddSUMSToVOD()
		if err != nil {
			logger.Err(err).Msgf("manifest sender failed at AddSUMSToVOD for vin %s", vin)
			return err
		}
	} else {
		logger.Err(err).Msgf("manifest sender failed at Validation for vin %s", vin)
		return err
	}
	hmiManifest.Scrub(common.HMI)

	client := m.Redis.GetFromPool()
	defer client.Close()
	// If HasSelfDownload, we are not yet ready to send to t box, so we wake the car if the update is forced
	if manifest.HasSelfDownload() && m.sms != nil {
		// The function that calls this one handles canceling the manifest
		var msgID string
		res, err := carcommand.QueueSMSWakeUp(vin, true, client, m.db.GetCars(), m.sms)
		if err != nil || res == nil || !res.SentSuccessful {
			logger.Err(err).Msgf("Attendant:manifest sender failed at sendSMSWakeUp for vin %s", vin)
			msgID = m.gen.GetString(10)
			defer m.selfKafkaCallback(msgID)
		} else {
			msgID = res.SmsMsgID
		}

		err = m.ka.SendFirstKeepAwakeMessage(vin)
		if err != nil {
			logger.Err(err).Msgf("failed to SendFirstKeepAwakeMessage on msgID %s", msgID)
		}
		m.queueManifest(vin, hmiManifest)

		fileID, ok := fileKeyBypass[updateManifestID]
		if ok {
			logger.Info().Str("VIN", vin).Msg("Queueing SendFileKeys")
			time.AfterFunc(time.Second*3, func() { sendFileKeys(vin, fileID) })
		}

		// Do not send manifest to TBOX, wait until ICC has finished downloading
		return err
	}

	trex := manifestsender.NewTBOXManifestSender(client, m.conf, m.db, services.GetSMSClient(), cds)
	defer trex.Close()
	//smsID, err := trex.ProcessSoftwareUpdate(vin, manifest)
	_, err = trex.ProcessSoftwareUpdate(vin, manifest, services.GetDB().GetCarConfigData())
	logger.Debug().Msgf("Send HMI manifest to  %s ", vin)
	m.queueManifest(vin, hmiManifest)

	// We managed to send an sms, so we want to continue with an sms delivered check
	// if smsID != "" {
	// 	err = m.setManifestCache(smsID, ManifestCachedPoint{
	// 		VIN:         vin,
	// 		ManifestID:  manifest.ID,
	// 		Destination: m.destination(manifest),
	// 	})
	// 	if err != nil {
	// 		return err
	// 	}
	// }
	return err
}

// So this flow of continue will be activated at two different times, possible multiple times.
// 1) When a new car_update comes through and the .hasdownload is true, and then again when the manifest is being sent to the tbox
func (m *ManifestSender) ContinueTBOXSend(id string, data []byte) (err error) {
	// After a text has been succesfully delivered, we can now continue the update
	// Check the status and make sure that is is success, otherwise fail and set the update status as failed
	var msgStatus tmobile.MessageStatus
	err = json.Unmarshal(data, &msgStatus)
	if err != nil {
		return
	}

	return nil
}

func (m *ManifestSender) setManifestCache(msgID string, cache ManifestCachedPoint) (err error) {
	client := m.Redis.GetFromPool()
	defer client.Close()
	return client.Set(msgIDToRedisKey(msgID), cache)
}

type ManifestCachedPoint struct {
	VIN         string
	ManifestID  int64
	Destination string
}

func msgIDToRedisKey(msgID string) (redisKey string) {
	return fmt.Sprintf("manifest_tbox_send_cache:%s", msgID)
}

func (m *ManifestSender) addVOD(manifest *common.UpdateManifest, vin string) (map[string]string, error) {
	cds, err := uhelpers.GetCDS(m.conf, services.GetDB().GetCarConfigData(), vin)
	if err != nil {
		return nil, err
	}

	if vod, ok := cds["VOD"]; ok && vod != "" {
		manifest.VOD = vod
	} else {
		manifest.VOD = defaultVOD
	}

	return cds, nil
}

// When car is a virtual trex, we are going to produce to the kafka message queue as if the sms service did it
func (m *ManifestSender) selfKafkaCallback(messageID string) {
	producer, err := services.GetKafkaProducer()
	if err != nil {
		logger.Err(err).Send()
		return
	}

	messageStatus := &kafka_grpc.GRPC_AttendantPayload_MessageStatus{
		MessageStatus: &kafka_grpc.MessageStatus{
			MessageId: messageID,
			Status:    kafka_grpc.EmumStatus_DELIVERED,
		},
	}
	kafkaMSG := kafka_grpc.GRPC_AttendantPayload{
		Handler: "sms_delivery_status_manifest",
		Data:    messageStatus,
	}

	binaryPayload, _ := proto.Marshal(&kafkaMSG)
	err = producer.ProduceBinary(kafka.AttendantServiceGRPCKafka, "4:Service", binaryPayload, nil)
	if err != nil {
		err = errors.WithStack(err)
		logger.Err(err).Msgf("failed to produce kafka message for sms id %s", messageID)
	}
}

func (m *ManifestSender) Release() {
	m.Redis = nil
	m.db = nil
	m.conf = nil
}

var fpParams FingerprintParamer
var fpParamsOnce sync.Once

func SetFPParams(fpp FingerprintParamer) {
	fpParams = fpp
}

func GetFPParams() FingerprintParamer {
	fpParamsOnce.Do(func() {
		if fpParams == nil {
			fpParams = &fingerprintParams{
				serialNum: envtool.GetEnv("OTA_MANIFEST_SERIAL", "00000000000000000"),
			}
		}
	})

	return fpParams
}

type fingerprintParams struct {
	serialNum string
}

func (p *fingerprintParams) ManifestSerial() string {
	return p.serialNum
}

func (p *fingerprintParams) CurTime() time.Time {
	return time.Now().UTC()
}

type FingerprintParamer interface {
	ManifestSerial() string
	CurTime() time.Time
}

// Mega hack mode

func sendFileKeys(vin string, fileID string) {
	logger.Info().Str("VIN", vin).Msg("Sending SendFileKeys")
	db := services.GetDB()
	err := GetFileKeys(db, common.HMI, vin, []byte(`{"file_ids": ["`+fileID+`"]}`))
	if err != nil {
		logger.Err(err).Str("VIN", vin).Str("file ID", fileID).Msg("Failed to SendFileKeys")
	}
}
