package common

import (
	"encoding/json"
	"log"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/google/uuid"
)

type ConsumerPayloadInterface interface {
	GetHandler() string
	GetData() []byte
}

type ConsumerPayload struct {
	Handler string
	Data    []byte
}

func (cp *ConsumerPayload) GetHandler() string {
	return cp.Handler
}

func (cp *ConsumerPayload) GetData() []byte {
	return cp.Data
}

// #####################################################################################################
// ################################## Depot ############################################################
// #####################################################################################################
func DepotRouteTRexPayload(payload *kafka_grpc.GRPC_DepotPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "init":
		data := GRPCToMessage(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	}
	return message, nil
}

func DepotRouteHMIPayload(payload *kafka_grpc.GRPC_DepotPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "init":
		data := GRPCToHMISession(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	}
	return message, nil
}

func DepotRouteMobilePayload(payload *kafka_grpc.GRPC_DepotPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	return message, nil
}

func GRPCToMessage(payload *kafka_grpc.GRPC_DepotPayload) map[string]string {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_DepotPayload_InitPayload:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_DepotPayload_InitPayload)

	return data.InitPayload.Data
}

func GRPCToHMISession(payload *kafka_grpc.GRPC_DepotPayload) *HMISessionData {
	if payload.Data == nil {
		return nil
	}
	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_DepotPayload_HmiSession:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_DepotPayload_HmiSession)
	return &HMISessionData{
		SessionID: data.HmiSession.SessionId,
		VIN:       data.HmiSession.Vin,
		Salt:      data.HmiSession.Salt,
	}
}

// #####################################################################################################
// ################################## End Depot ########################################################
// #####################################################################################################

// #####################################################################################################
// ################################## Attendant ########################################################
// #####################################################################################################
func AttendantRouteTRexPayload(payload *kafka_grpc.GRPC_AttendantPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "car_state":
		data := GRPCToCarState(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	case "car_update_status", "car_update_download", "car_update_install":
		data := GRPCToCarUpdate(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	case "get_filekeys":
		data := GRPCToCarFileKeysRequest(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	case "ecc_keys":
		data := GRPCToCarUpdateRequest(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	}
	return message, nil
}

func AttendantRouteHMIPayload(payload *kafka_grpc.GRPC_AttendantPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "update_approve":
		if payload.Data != nil {
			data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_UpdateApprove)
			if data == nil {
				break
			}
			m := map[string]int{
				"id": int(data.UpdateApprove.Id),
			}
			message.Data, _ = json.Marshal(m)
		}
	case "car_update_status", "car_update_download", "car_update_install":
		data := GRPCToCarUpdate(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	case "get_filekeys":
		data := GRPCToCarFileKeysRequest(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	}
	return message, nil
}

func AttendantRouteMobilePayload(payload *kafka_grpc.GRPC_AttendantPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "update_approve":
		if payload.Data != nil {
			data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_UpdateApprove)
			if data == nil || data.UpdateApprove == nil {
				break
			}
			m := map[string]int{
				"id": int(data.UpdateApprove.Id),
			}
			message.Data, _ = json.Marshal(m)
		}
	case "updates_get":
		if payload.Data != nil {
			data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_UpdateGet)
			if data == nil || data.UpdateGet == nil {
				break
			}
			m := map[string]string{
				"vin": data.UpdateGet.Vin,
			}
			message.Data, _ = json.Marshal(m)
		}
	}
	return message, nil
}

func AttendantRouteServicePayload(payload *kafka_grpc.GRPC_AttendantPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "send_manifest":
		data := GRPCToUpdateManifest(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	case "order_updated":
		data := GRPCToOrder(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	}
	return message, nil

}

func GRPCToCarUpdateRequest(payload *kafka_grpc.GRPC_AttendantPayload) *CarUpdateRequest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_AttendantPayload_CarUpdateRequest:
		break
	default:
		return nil
	}

	data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_CarUpdateRequest)
	if data == nil {
		return nil
	}

	carReq := &CarUpdateRequest{
		CarUpdateID: data.CarUpdateRequest.CarUpdateId,
	}
	return carReq
}

func GRPCToCarUpdate(payload *kafka_grpc.GRPC_AttendantPayload) *CarUpdateProgress {

	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_AttendantPayload_CarUpdateProgress:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_CarUpdateProgress)
	if data == nil {
		return nil
	}

	carProg := &CarUpdateProgress{
		FileCurrent:    data.CarUpdateProgress.FileCurrent,
		FileTotal:      data.CarUpdateProgress.FileTotal,
		PackageCurrent: data.CarUpdateProgress.PkgCurrent,
		PackageTotal:   data.CarUpdateProgress.PkgTotal,
		InstalledFiles: int(data.CarUpdateProgress.InstalledFiles),
		TotalFiles:     int(data.CarUpdateProgress.TotalFiles),
		CarUpdateID:    data.CarUpdateProgress.CarUpdateID,
		ECU:            data.CarUpdateProgress.Ecu,
		Status:         data.CarUpdateProgress.Status,
		Info:           data.CarUpdateProgress.Info,
		ErrorCode:      int(data.CarUpdateProgress.ErrCode),
	}
	return carProg
}

func GRPCToCarFileKeysRequest(payload *kafka_grpc.GRPC_AttendantPayload) *FileKeysRequest {

	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_AttendantPayload_FileKeyReq:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_FileKeyReq)
	if data == nil {
		return nil
	}

	fileReq := &FileKeysRequest{
		FileIDs: data.FileKeyReq.FileIDs,
	}
	return fileReq
}

func GRPCToECCKeys(payload *kafka_grpc.GRPC_AttendantPayload) []ECCKeys {

	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_AttendantPayload_Keys:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_Keys)
	if data == nil {
		return nil
	}
	var keys []ECCKeys
	for _, e := range data.Keys.EccKeys {
		output := toECCKey(e)
		keys = append(keys, *output)
	}
	return keys
}

func toECCKey(req *kafka_grpc.ECCKey) *ECCKeys {
	if req == nil {
		return nil
	}
	e := &ECCKeys{
		ECU: req.Ecu,
		Env: req.Env,
	}

	if req.PubKeyLevel_1 != nil && req.PubKeyLevel_1.Data != nil {
		e.PubKey1 = (*BinaryHex)(&req.PubKeyLevel_1.Data)
	}
	if req.PubKeyLevel_2 != nil && req.PubKeyLevel_2.Data != nil {
		e.PubKey2 = (*BinaryHex)(&req.PubKeyLevel_2.Data)
	}
	if req.PubKeyLevel_3 != nil && req.PubKeyLevel_3.Data != nil {
		e.PubKey3 = (*BinaryHex)(&req.PubKeyLevel_3.Data)
	}
	if req.Level_1 != nil && req.Level_1.Data != nil {
		e.PrivKey1 = (*BinaryHex)(&req.Level_1.Data)
	}
	if req.Level_2 != nil && req.Level_2.Data != nil {
		e.PrivKey2 = (*BinaryHex)(&req.Level_2.Data)
	}
	if req.Level_3 != nil && req.Level_3.Data != nil {
		e.PrivKey3 = (*BinaryHex)(&req.Level_3.Data)
	}

	if req.Created != nil {
		t := milliToDate(*req.Created)
		e.CreatedAt = &t
	}
	if req.Updated != nil {
		t := milliToDate(*req.Updated)
		e.UpdatedAt = &t
	}
	return e

}

func GRPCToCarState(payload *kafka_grpc.GRPC_AttendantPayload) *CarStateUpdate {

	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_AttendantPayload_CarUpdateStatus:
		break
	default:
		return nil
	}

	data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_CarUpdateStatus)
	if data == nil {
		return nil
	}
	var ecus map[string]CarECU
	for key, val := range data.CarUpdateStatus.Ecus {
		if ecus == nil {
			ecus = make(map[string]CarECU)
		}
		carECU := CarECU{
			VIN:               val.Vin,
			ECU:               val.Ecu,
			Version:           val.SwVersion,
			SerialNumber:      val.SerialNumber,
			HWVersion:         val.HwVersion,
			BootLoaderVersion: val.BootLoaderVersion,
			Fingerprint:       val.Fingerprint,
			Config:            val.CodeDataString,
			Vendor:            val.Vendor,
			SupplierSWVersion: val.SupplierSwVersion,
			Epoch_usec:        val.EpochUsec,
			ASSYNumber:        val.AssyNumber,
		}
		if val.CreatedAt != nil {
			t := milliToDate(*val.CreatedAt)
			carECU.CreatedAt = &t
		}
		if val.UpdatedAt != nil {
			t := milliToDate(*val.UpdatedAt)
			carECU.UpdatedAt = &t
		}
		ecus[key] = carECU
	}
	carState := &CarStateUpdate{
		ECUs: ecus,
	}
	return carState
}

func GRPCToUpdateManifest(payload *kafka_grpc.GRPC_AttendantPayload) *UpdateManifest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_AttendantPayload_UpdateManifest:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_UpdateManifest)
	updateManifest := &UpdateManifest{
		ID:              data.UpdateManifest.Id,
		Name:            data.UpdateManifest.Name,
		Version:         data.UpdateManifest.Version,
		Description:     data.UpdateManifest.Description,
		ReleaseNotes:    data.UpdateManifest.ReleaseNotes,
		ECUList:         data.UpdateManifest.EcuList,
		Fingerprint:     data.UpdateManifest.Fingerprint,
		CarUpdateID:     data.UpdateManifest.CarUpdateId,
		RollbackEnabled: data.UpdateManifest.Rollback,
		Type:            data.UpdateManifest.Type,
		VOD:             data.UpdateManifest.Vod,
		ManifestType:    UpdateManifestType(data.UpdateManifest.ManifestType),
		Env:             data.UpdateManifest.Env,
		UpdateDuration:  int(data.UpdateManifest.UpdateDuration),
		MaxAttempts:     int(data.UpdateManifest.MaxAttempts),
		Active:          data.UpdateManifest.Active,
		Country:         data.UpdateManifest.Country,
		BodyType:        data.UpdateManifest.BodyType,
		Restraint:       data.UpdateManifest.Restraint,
		PowerTrain:      data.UpdateManifest.Powertrain,
		Model:           data.UpdateManifest.Model,
		Trim:            data.UpdateManifest.Trim,
		Year:            int(data.UpdateManifest.Year),
		SUMS:            data.UpdateManifest.Sums,
	}
	if data.UpdateManifest.Created != nil {
		t := milliToDate(*data.UpdateManifest.Created)
		updateManifest.CreatedAt = &t
	}

	if data.UpdateManifest.Updated != nil {
		t := milliToDate(*data.UpdateManifest.Updated)
		updateManifest.UpdatedAt = &t
	}

	var updateManifestECU []*UpdateManifestECU

	updateManifestECU = GRPCUpdateManifestECUToUpdateManifestECU(data.UpdateManifest.EcuUpdates, updateManifestECU)
	updateManifest.ECUs = updateManifestECU

	return updateManifest
}

func GRPCUpdateManifestECUToUpdateManifestECU(input []*kafka_grpc.UpdateManifestECU, output []*UpdateManifestECU) []*UpdateManifestECU {
	if len(input) == 0 {
		return nil
	}
	for _, u := range input {
		update := &UpdateManifestECU{
			ID:                u.Id,
			UpdateManifestID:  u.ManifestId,
			ECU:               u.Name,
			Version:           u.Version,
			CurrentVersion:    u.CurrentVersion,
			HWVersion:         u.HwVersion,
			HWVersions:        u.HwVersions,
			ConfigurationMask: u.ConfigurationMask,
			Configuration:     u.Configuration,
			SelfDownload:      u.SelfDownload,
			Mode:              u.Mode,
			InstallPriority:   int(u.InstallPriority),
		}

		if u.Created != nil {
			t := milliToDate(*u.Created)
			update.CreatedAt = &t
		}

		if u.Updated != nil {
			t := milliToDate(*u.Updated)
			update.UpdatedAt = &t
		}
		update.ECCKeys = toECCKey(u.EccKeys)
		if len(u.Files) > 0 {
			fileInBytes := toBinaryArray(u.Files)
			if len(fileInBytes) > 0 {
				var files []*UpdateManifestFile
				err := json.Unmarshal(fileInBytes, &files)
				if err != nil {
					log.Println(err)
					log.Println(string(fileInBytes))
					logger.Warn().Msgf("unable to parse Files of %s", string(fileInBytes))
				}
				update.Files = files
			}
		}
		if len(u.Rollback) > 0 {
			update.Rollback = make([]*UpdateManifestECU, 0, len(u.Rollback))
			update.Rollback = GRPCUpdateManifestECUToUpdateManifestECU(u.Rollback, update.Rollback)
		}

		output = append(output, update)

	}
	return output
}

// func UpdateNotificationToGRPC(notification *UpdateAvailableNotification) *kafka_grpc.GRPC_AttendantPayload {
// 	if notification == nil {
// 		return nil
// 	}

// 	updateManifest := &kafka_grpc.UpdateManifest{
// 		Id:
// 		Name:         notification.Name,
// 		Description:  notification.Description,
// 		Version:      notification.Version,
// 		ReleaseNotes: notification.ReleaseNotes,
// 		CarUpdateId:  notification.CarUpdateID,

// 	}

// 	grpcPayload := &kafka_grpc.GRPC_AttendantPayload{
// 		Data: &kafka_grpc.GRPC_AttendantPayload_UpdateManifest{
// 			UpdateManifest: updateManifest,
// 		},
// 	}

// 	return grpcPayload
// }

func milliToDate(timestamp int64) time.Time {
	seconds := timestamp / 1000
	nanoseconds := (timestamp % 1000) * int64(time.Millisecond)
	return time.Unix(seconds, nanoseconds)
}

// #####################################################################################################
// ################################## End Attendant ########################################################
// #####################################################################################################

// #####################################################################################################
// ################################## Valet ########################################################
// #####################################################################################################
func ValetRouteTRexPayload(payload *kafka_grpc.GRPC_ValetPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "departure_schedule":
		message.Data = toBinaryArray(GRPCToDepartureSchedule(payload))
	case "charge_settings":
		message.Data = toBinaryArray(GRPCToChargeSetting(payload))
	case "charging_command":
		if payload.Data == nil {
			break
		}
		data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_ChCMD)
		message.Data = toBinaryArray(data.ChCMD)
	}
	return message, nil
}

func ValetRouteHMIPayload(payload *kafka_grpc.GRPC_ValetPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "ble_key":
		data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_BLEKey)
		message.Data = toBinaryArray(data.BLEKey)
	case "profiles":
		break
	case "settings_update":
		message.Data = toBinaryArray(GRPCToHMISettingsUpdate(payload))
	case "map_history":
		message.Data = toBinaryArray(GRPCToHMIMapHistory(payload))
	case "user_pois":
		message.Data = toBinaryArray(GRPCToHMIPOIsMessage(payload))
	case "consent":
		message.Data = toBinaryArray(GRPCToUserConsentFromHMI(payload))
	case "profile_delete":
		message.Data = toBinaryArray(GRPCToJSONHMIDeleteProfile(payload))
	}
	return message, nil
}

func ValetRouteMobilePayload(payload *kafka_grpc.GRPC_ValetPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "charge_settings_get", "departure_schedule_get":
		message.Data = toBinaryArray(GRPCToEmptyMessageFromMobile(payload))
	case "remote_command":
		message.Data = toBinaryArray(GRPCToRemotCMD(payload))
	case "car_locations", "map_history_get", "profiles", "store_inventory", "user_pois_get":
		break // no payload for these handlers
	case "digital_twin":
		message.Data = toBinaryArray(GRPCToDigitalTwinRequest(payload))
	case "map_destination":
		message.Data = toBinaryArray(GRPCToMapDestinationRequest(payload))
	case "map_route":
		message.Data = toBinaryArray(GRPCToMapRouteRequest(payload))
	case "map_history_add":
		message.Data = toBinaryArray(GRPCToMapHistory(payload))
	case "settings_update":
		message.Data = toBinaryArray(GRPCToMobileSettingsUpdate(payload))
	case "charge_settings":
		message.Data = toBinaryArray(GRPCToMobileChargeSetting(payload))
	case "store_purchase":
		message.Data = toBinaryArray(GRPCToStorePurchases(payload))
	case "user_poi_create":
		message.Data = toBinaryArray(GRPCToPointOfInterest(payload))
	case "user_poi_edit":
		message.Data = toBinaryArray(GRPCToMobilePOIEditMessage(payload))
	case "user_poi_delete":
		message.Data = toBinaryArray(GRPCToMobilePOIDeleteMessage(payload))
	case "departure_schedule":
		message.Data = toBinaryArray(GRPCToMobileDepartureSchedule(payload))
	case "wake_car":
		message.Data = toBinaryArray(GRPCToWakeCar(payload))
	case "manual_issue":
		message.Data = toBinaryArray(GRPCToAddIssueRequest(payload))
	}
	return message, nil
}

func ValetRouteServicePayload(payload *kafka_grpc.GRPC_ValetPayload) (*ConsumerPayload, error) {
	message := &ConsumerPayload{
		Handler: payload.Handler,
	}
	switch payload.Handler {
	case "remote_command":
		data := GRPCToRemotCMD(payload)
		if data == nil {
			break
		}
		message.Data, _ = json.Marshal(data)
	}
	return message, nil

}

func GRPCToRemotCMD(payload *kafka_grpc.GRPC_ValetPayload) *RemoteCommandRequest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_RemoteCmd:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_RemoteCmd)

	b := toBinaryArray(data.RemoteCmd)
	if len(b) > 0 {
		var cmd RemoteCommandRequest
		err := json.Unmarshal(b, &cmd)
		if err != nil {
			return nil
		}
		return &cmd
	}
	return nil
}

func GRPCToEmptyMessageFromMobile(payload *kafka_grpc.GRPC_ValetPayload) *EmptyMessageFromMobile {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_EmptyMsg:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_EmptyMsg)
	emtMsq := &EmptyMessageFromMobile{
		VIN: data.EmptyMsg.Vin,
	}
	return emtMsq
}

func GRPCToDigitalTwinRequest(payload *kafka_grpc.GRPC_ValetPayload) *DigitalTwinRequest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_DigitalTwinReq:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_DigitalTwinReq)
	obj := &DigitalTwinRequest{
		VIN: data.DigitalTwinReq.Vin,
	}
	return obj
}

func GRPCToMapDestinationRequest(payload *kafka_grpc.GRPC_ValetPayload) *MapDestinationRequest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_MapDestReq:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_MapDestReq)
	obj := &MapDestinationRequest{
		VIN:  data.MapDestReq.Vin,
		Name: data.MapDestReq.Name,
	}
	if data.MapDestReq.Address != nil {
		obj.Address = &TomTomAddress{}
		obj.Address.StreetName = data.MapDestReq.Address.StreetName
		obj.Address.StreetNumber = data.MapDestReq.Address.StreetNumber
		obj.Address.LocalName = data.MapDestReq.Address.LocalName
		obj.Address.PostalCode = data.MapDestReq.Address.PostalCode
		obj.Address.CountrySubdivisionName = data.MapDestReq.Address.CountrySubdivisionName
		obj.Address.CountryCodeIso3 = data.MapDestReq.Address.CountryCodeIso3
	}
	if data.MapDestReq.Coordinates != nil {
		obj.Coordinates = MapCoordinates{
			Latitude:  data.MapDestReq.Coordinates.Latitude,
			Longitude: data.MapDestReq.Coordinates.Longitude,
		}
	}
	return obj
}

func GRPCToMapRouteRequest(payload *kafka_grpc.GRPC_ValetPayload) *MapRouteRequest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_MapRouteReq:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_MapRouteReq)
	obj := &MapRouteRequest{
		VIN: data.MapRouteReq.Vin,
	}
	if len(data.MapRouteReq.Route) > 0 {
		var routes []MapCoordinates
		for _, r := range data.MapRouteReq.Route {
			routes = append(routes, MapCoordinates{
				Latitude:  r.Latitude,
				Longitude: r.Latitude,
			})
		}
		obj.Route = routes
	}
	if len(data.MapRouteReq.Waypoints) > 0 {
		var points []MapWaypoint
		for _, p := range data.MapRouteReq.Waypoints {
			point := MapWaypoint{
				Type:  p.Type,
				Title: p.Title,
			}
			point.Latitude = p.Coordinates.Latitude
			point.Longitude = p.Coordinates.Longitude
			points = append(points, point)
		}
		obj.Waypoints = points
	}
	return obj
}

func GRPCToMapHistory(payload *kafka_grpc.GRPC_ValetPayload) *MapHistory {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_MapHistory:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_MapHistory)
	obj := &MapHistory{
		Name:        data.MapHistory.Name,
		Description: data.MapHistory.Description,
	}
	if data.MapHistory.Location != nil {
		obj.Location = &MapCoordinates{
			Latitude:  data.MapHistory.Location.Latitude,
			Longitude: data.MapHistory.Location.Longitude,
		}
	}
	return obj
}

func GRPCToMobileDepartureSchedule(payload *kafka_grpc.GRPC_ValetPayload) *MobileDepartureSchedule {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_MobileDepartureSchedule:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_MobileDepartureSchedule)
	obj := &MobileDepartureSchedule{
		VIN: data.MobileDepartureSchedule.Vin,
	}
	if data.MobileDepartureSchedule.DepartureSchedule != nil {
		obj.DepartureSchedule = DepartureSchedule{
			NextDayDeparture: data.MobileDepartureSchedule.DepartureSchedule.NextDayDeparture,
		}
		if len(data.MobileDepartureSchedule.DepartureSchedule.DepartureDays) > 0 {
			var days []DepartureDay
			for _, d := range data.MobileDepartureSchedule.DepartureSchedule.DepartureDays {
				days = append(days, DepartureDay{
					DayOfWeek: d.DayOfWeek,
					Time:      d.Time,
				})
			}
			obj.DepartureSchedule.DepartureDays = days
		}
	}
	return obj
}

func GRPCToAddIssueRequest(payload *kafka_grpc.GRPC_ValetPayload) *AddIssueRequest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_AddIssueReq:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_AddIssueReq)
	obj := &AddIssueRequest{
		Images: data.AddIssueReq.Images,
	}
	if data.AddIssueReq.Issue != nil {
		obj.VIN = data.AddIssueReq.Issue.Vin
		obj.Description = data.AddIssueReq.Issue.Description
		obj.DriverID = data.AddIssueReq.Issue.DriverId
		obj.ID = int(data.AddIssueReq.Issue.Id)
		obj.Title = data.AddIssueReq.Issue.Title
		obj.Timestamp = milliToDate(data.AddIssueReq.Issue.Timestamp)
		if len(data.AddIssueReq.Issue.Images) > 0 {
			var images []IssueImage
			for _, img := range data.AddIssueReq.Issue.Images {
				iimg := IssueImage{
					ID:      int(img.Id),
					Image:   img.Image,
					IssueID: int(img.IssueId),
				}
				images = append(images, iimg)
			}
			obj.IssueImages = images
		}
	}
	return obj
}

func GRPCToWakeCar(payload *kafka_grpc.GRPC_ValetPayload) *EmptyMessageFromMobile {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_Wakecar:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_Wakecar)
	obj := &EmptyMessageFromMobile{
		VIN: data.Wakecar.Vin,
	}
	return obj
}

func GRPCToMobilePOIEditMessage(payload *kafka_grpc.GRPC_ValetPayload) *MobilePOIEditMessage {
	if payload.Data == nil {
		return nil
	}
	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_PoiEdit:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_PoiEdit)
	obj := &MobilePOIEditMessage{
		OldName: data.PoiEdit.OldName,
	}
	if data.PoiEdit.UserPoi != nil {
		pointOdInterest := &PointOfInterest{
			Name: data.PoiEdit.UserPoi.Name,
		}
		if data.PoiEdit.UserPoi != nil {
			pointOdInterest.Location = POILocation{
				Latitude:  data.PoiEdit.UserPoi.Location.Latitude,
				Longitude: data.PoiEdit.UserPoi.Location.Longitude,
			}
		}
	}
	return obj
}

func GRPCToMobilePOIDeleteMessage(payload *kafka_grpc.GRPC_ValetPayload) *MobilePOIDeleteMessage {
	if payload.Data == nil {
		return nil
	}
	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_PoiDelete:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_PoiDelete)
	obj := &MobilePOIDeleteMessage{
		Name: data.PoiDelete.Name,
	}
	return obj
}

func GRPCToPointOfInterest(payload *kafka_grpc.GRPC_ValetPayload) *PointOfInterest {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_PoiCreate:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_PoiCreate)
	obj := &PointOfInterest{
		Name: data.PoiCreate.Name,
	}
	if data.PoiCreate.Location != nil {
		obj.Location = POILocation{
			Latitude:  data.PoiCreate.Location.Latitude,
			Longitude: data.PoiCreate.Location.Longitude,
		}
	}
	return obj
}

func GRPCToStorePurchases(payload *kafka_grpc.GRPC_ValetPayload) *StorePurchases {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_StorePurchases:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_StorePurchases)
	obj := &StorePurchases{
		VIN: data.StorePurchases.Vin,
	}
	if len(data.StorePurchases.Purchases) > 0 {
		var purchases []StorePurchaseItem
		for _, p := range data.StorePurchases.Purchases {
			uuidObj, err := uuid.Parse(p.Id)
			if err != nil {
				continue
			}
			purchase := StorePurchaseItem{
				ID: uuidObj,
			}
			purchases = append(purchases, purchase)
		}
		obj.Purchases = purchases
	}
	return obj
}

func GRPCToMobileSettingsUpdate(payload *kafka_grpc.GRPC_ValetPayload) *MobileSettingsUpdate {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_SettingsUpdate:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_SettingsUpdate)
	obj := &MobileSettingsUpdate{
		VIN: data.SettingsUpdate.Vin,
	}
	if len(data.SettingsUpdate.Settings) > 0 {
		var settings []CarSetting
		for _, s := range data.SettingsUpdate.Settings {
			setting := CarSetting{
				VIN:      s.Vin,
				DriverID: s.DriverId,
				Name:     s.Name,
				Value:    s.Value,
				Type:     s.Type,
			}
			if s.Created != nil {
				t := milliToDate(*s.Created)
				setting.CreatedAt = &t
			}
			if s.Updated != nil {
				t := milliToDate(*s.Updated)
				setting.UpdatedAt = &t
			}
			settings = append(settings, setting)
		}
		obj.Settings = settings
	}
	return obj
}

func GRPCToMobileChargeSetting(payload *kafka_grpc.GRPC_ValetPayload) *MobileChargeSetting {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_MobileChargeSetting:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_MobileChargeSetting)
	obj := &MobileChargeSetting{
		VIN: data.MobileChargeSetting.Vin,
	}
	if data.MobileChargeSetting.ChargeSettings != nil {
		obj.ChargeSettings = ChargeSettings{
			ChargeLimit: int(data.MobileChargeSetting.ChargeSettings.ChargeLimit),
			MaxCurrent:  int(data.MobileChargeSetting.ChargeSettings.MaxCurrent),
		}
		if data.MobileChargeSetting.ChargeSettings.OffPeakCcharging != nil {
			obj.ChargeSettings.OffPeakCharging = &OffPeakCharging{
				Start: milliToDate(data.MobileChargeSetting.ChargeSettings.OffPeakCcharging.Start),
				End:   milliToDate(data.MobileChargeSetting.ChargeSettings.OffPeakCcharging.End),
			}
		}

		if data.MobileChargeSetting.ChargeSettings.MinCharge != nil {
			t := int(*data.MobileChargeSetting.ChargeSettings.MinCharge)
			obj.ChargeSettings.MinCharge = &t
		}
	}
	return obj
}

func GRPCToHMISettingsUpdate(payload *kafka_grpc.GRPC_ValetPayload) *HMISettingsUpdate {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_HmiSettingsUpdate:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_HmiSettingsUpdate)
	obj := &HMISettingsUpdate{
		DriverID: data.HmiSettingsUpdate.DriverId,
	}
	if len(data.HmiSettingsUpdate.Settings) > 0 {
		var carSettings []CarSetting
		for _, s := range data.HmiSettingsUpdate.Settings {
			carSetting := CarSetting{
				VIN:      s.Vin,
				DriverID: s.DriverId,
				Name:     s.Name,
				Type:     s.Type,
				Value:    s.Value,
			}
			if s.Created != nil {
				t := milliToDate(*s.Created)
				carSetting.CreatedAt = &t
			}
			if s.Updated != nil {
				t := milliToDate(*s.Updated)
				carSetting.UpdatedAt = &t
			}

			carSettings = append(carSettings, carSetting)
		}
		obj.Settings = carSettings
	}
	return obj
}

func GRPCToHMIMapHistory(payload *kafka_grpc.GRPC_ValetPayload) *HMIMapHistory {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_HmiMapHistory:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_HmiMapHistory)
	obj := &HMIMapHistory{
		DriverID: data.HmiMapHistory.DriverId,
	}
	if len(data.HmiMapHistory.Searches) > 0 {
		var searches []MapHistory
		for _, s := range data.HmiMapHistory.Searches {
			search := MapHistory{
				Name:        s.Name,
				Description: s.Description,
			}
			if s.Location != nil {
				search.Location = &MapCoordinates{
					Latitude:  s.Location.Latitude,
					Longitude: s.Location.Longitude,
				}
			}
			searches = append(searches, search)
		}
		obj.Searches = searches
	}
	return obj
}

func GRPCToHMIPOIsMessage(payload *kafka_grpc.GRPC_ValetPayload) *HMIPOIsMessage {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_HmiPOIsMessage:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_HmiPOIsMessage)
	obj := &HMIPOIsMessage{
		DriverID: data.HmiPOIsMessage.DriverId,
	}
	if len(data.HmiPOIsMessage.UserPOIs) > 0 {
		var pois []PointOfInterest
		for _, p := range data.HmiPOIsMessage.UserPOIs {
			poi := PointOfInterest{
				Name: p.Name,
			}
			if p.Location != nil {
				poi.Location = POILocation{
					Latitude:  p.Location.Latitude,
					Longitude: p.Location.Longitude,
				}
			}
			pois = append(pois, poi)
		}
		obj.UserPOIs = pois
	}
	return obj
}

func GRPCToUserConsentFromHMI(payload *kafka_grpc.GRPC_ValetPayload) []*UserConsentFromHMI {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_UserConsentFromHMI:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_UserConsentFromHMI)
	if data.UserConsentFromHMI != nil && len(data.UserConsentFromHMI.UserConsent) > 0 {
		var userConsentFromHMI []*UserConsentFromHMI
		for _, d := range data.UserConsentFromHMI.UserConsent {
			obj := &UserConsentFromHMI{
				Name:     d.Name,
				Accept:   d.Accept,
				DriverID: d.DriverId,
			}
			userConsentFromHMI = append(userConsentFromHMI, obj)
		}

		return userConsentFromHMI
	}
	return nil
}

func GRPCToJSONHMIDeleteProfile(payload *kafka_grpc.GRPC_ValetPayload) *JSONHMIDeleteProfile {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_HmiDeleteProfile:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_HmiDeleteProfile)
	obj := &JSONHMIDeleteProfile{
		DriverID: data.HmiDeleteProfile.DriverId,
	}
	return obj
}

func GRPCToDepartureSchedule(payload *kafka_grpc.GRPC_ValetPayload) *DepartureSchedule {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_DepartureSchedule:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_DepartureSchedule)
	obj := &DepartureSchedule{
		NextDayDeparture: data.DepartureSchedule.NextDayDeparture,
	}
	if len(data.DepartureSchedule.DepartureDays) > 0 {
		var days []DepartureDay
		for _, d := range data.DepartureSchedule.DepartureDays {
			days = append(days, DepartureDay{
				DayOfWeek: d.DayOfWeek,
				Time:      d.Time,
			})
		}
		obj.DepartureDays = days
	}
	return obj
}

func GRPCToChargeSetting(payload *kafka_grpc.GRPC_ValetPayload) *ChargeSettings {
	if payload.Data == nil {
		return nil
	}

	switch payload.Data.(type) {
	case *kafka_grpc.GRPC_ValetPayload_ChargeSetting:
		break
	default:
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_ValetPayload_ChargeSetting)
	obj := &ChargeSettings{
		ChargeLimit: int(data.ChargeSetting.ChargeLimit),
		MaxCurrent:  int(data.ChargeSetting.MaxCurrent),
	}
	if data.ChargeSetting.OffPeakCcharging != nil {
		obj.OffPeakCharging = &OffPeakCharging{
			Start: milliToDate(data.ChargeSetting.OffPeakCcharging.Start),
			End:   milliToDate(data.ChargeSetting.OffPeakCcharging.End),
		}
	}
	if data.ChargeSetting.MinCharge != nil {
		t := int(*data.ChargeSetting.MinCharge)
		obj.MinCharge = &t
	}

	return obj
}

func toBinaryArray(data interface{}) []byte {
	if data == nil {
		return nil
	}
	bytes, _ := json.Marshal(data)
	if string(bytes) == "null" {
		return nil
	}
	return bytes
}

// it's mapping to contollers.DTCEntry
type CommonDTCEntry struct {
	CreatedAt time.Time `bson:"created_at"`
	VIN       string    `bson:"vin"`
	ECU       string    `json:"ecu" bson:"ecu"`
	DTC       uint64    `json:"dtc" bson:"dtc"`
	Status    uint8     `json:"status" bson:"status"`
	Timestamp time.Time `json:"timestamp" bson:"timestamp"`
	Speed     uint16    `json:"speed" bson:"speed"`
	Mileage   uint32    `json:"mileage" bson:"mileage"`
	Voltage   uint16    `json:"voltage" bson:"voltage"`

	SnapshotBase64 string `json:"snapshot,omitempty" bson:"snapshot,omitempty"`
}
