package handlers

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"otaupdate/services"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/common/actionlogger"
	"github.com/fiskerinc/cloud-services/pkg/httphandlers"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
	"github.com/fiskerinc/cloud-services/pkg/utils"
)

var OTAUpdateECUReplacement = map[string]string{
	"TBOX": "MCU",
	"PDU":  "OBC",
	"EPS":  "EPS1",
}

// Replace PDX ECUs to the names known by T.Rex.
func transformECUNames(pdxEcu []string) {
	for i := 0; i < len(pdxEcu); i++ {
		ecu := pdxEcu[i]
		if replacementECU, exists := OTAUpdateECUReplacement[ecu]; exists {
			pdxEcu[i] = replacementECU
		}
	}
}

var ModelToECUSecOC = map[string][]string{
	// '1' - Fisker Ocean One.
	"1": {"TBOX" /*T.Rex's MCU*/, "GW", "MCU_R" /*It's called EDU 'Electtric Drive Unit'*/, "MCU_F",
		"BMS", "PDU" /*T.Rex's OBC*/, "VCU", "ECC", "ADAS", "EPS", "BCM",
		"PKC", "FCM", "MRR", "CMRR_FL", "CMRR_FR", "CMRR_RL", "CMRR_RR"},
}
var parseRequest = func(request common.RemoteDiagnosticCommandRequest, w http.ResponseWriter) (interface{}, error) {
	if request.Command == "can_network" {
		var args common.RemoteCANNetworkCommandArgs
		args.Action = request.CANNetAction
		args.Timeout = request.Timeout
		return args, nil
	}
	if request.Command == "remote_ignition" {
		var args common.RemoteIgnitionCommandArgs
		args.Action = request.IgnitionAction
		args.Timeout = request.Timeout
		return args, nil
	}
	if request.Command == "read_ecu_versions" {
		var args common.RemoteReadVersionsCommandArgs
		args.ECUName = request.ECU
		return args, nil
	}
	var args common.RemoteResetDiagnosticCommandArgs
	args.ECUName = request.ECU
	//Don't need any keys for reset TBOX
	if request.ECU != "TBOX" {
		//XXX TBOX ECUs were divided in to MCU, T.Rex, and NAD.
		//Keys had already been inserted in DB with the name 'TBOX' for what is referred to as 'MCU' and so we must pull the key by that name.
		if request.ECU == "MCU" {
			request.ECU = "TBOX"
		}
		eccKeys, err := services.GetDB().GetECCKeys().SelectPrivateKeysByECUsEnv([]string{request.ECU}, "")
		if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound, loggerdataresp.PostgresNoRowsErrorCheck) {
			return nil, errors.New("")
		}
		if len(eccKeys) != 1 {
			utils.RespError(w, http.StatusNotFound, "ECC keys not found")
			return nil, errors.New("")
		}
		args.UDSKeys = &eccKeys[0]
		args.UDSKeys.ECU = ""
	}
	return args, nil
}

// HandleVehicleDiagnosticCommand godoc
// @Summary Send diagnostic command to car
// @Description Send diagnostic command to car
// @Accept  json
// @Produce  json
// @Param Authorization header string false "Bearer <ID token>"
// @Param Api-Key header string false "<API token>"
// @Param Command body common.RemoteDiagnosticCommandRequest true "Diagnostic command to send to cars"
// @Success 200 {object} common.JSONMessage
// @Failure 400 {object} common.JSONError "Bad request"
// @Failure 401 {object} common.JSONError "Unauthorized"
// @Failure 503 {object} common.JSONError "Service unavailable"
// @Router /vehiclediagnosticcommand [post]
func HandleVehicleDiagnosticCommand(w http.ResponseWriter, r *http.Request) {
	var request common.RemoteDiagnosticCommandRequest
	err := httphandlers.ParseRequest(r, &request)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}
	client := services.RedisClientPool().GetFromPool()
	defer client.Close()

	b, _ := json.Marshal(request)
	alDB := services.GetDB().GetActionLog()

	go func() {
		actionLog := actionlogger.ActionLog{
			VIN:            "",
			Action:         actionlogger.CarUpdate,
			UserIdentifier: httphandlers.GetClientID(r),
			CallLocation:   "github.com/fiskerinc/cloud-services/services/ota_update_go/handlers/vehicle_diagnostic_command.go",
			Description:    string(b),
		}

		for _, vin := range request.VINs {
			actionLog.VIN = vin
			err = alDB.Insert(actionLog)
			if err != nil {
				logger.Err(err).Msg("failed to insert action log inside HandleCarUpdateCancel")
			}
		}
	}()

	// Since this uses publish, if the car is not awake, the message is never received by the car
	SendToVin := func(vin string, data interface{}) {
		err = client.SafePublishMessage(
			common.TRex.Key(vin),
			common.Message{
				Handler: request.Command,
				Data:    data,
			},
		)
		loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable)
	}

	if request.Command == "write_secoc_key" {
		for _, vin := range request.VINs {
			var args common.RemoteUpdateSecOCCommandArgs

			// Fourth character in VIN is model.
			var ecus []string
			for _, ecu := range ModelToECUSecOC[string(vin[3])] {
				ecus = append(ecus, ecu)
			}
			eccKeys, err := services.GetDB().GetECCKeys().SelectPrivateKeysByECUsEnv(ecus, "")
			transformECUNames(ecus)
			args.ECUs = ecus
			if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound, loggerdataresp.PostgresNoRowsErrorCheck) {
				continue
			}
			args.UDSKeys = eccKeys
			for i := 0; i < len(args.UDSKeys); i++ {
				ecu := args.UDSKeys[i].ECU
				if replacementECU, exists := OTAUpdateECUReplacement[ecu]; exists {
					args.UDSKeys[i].ECU = replacementECU
				}
			}
			symKeys, err := services.GetDB().GetSymKeys().SelectByVIN(vin)
			if loggerdataresp.BadDataErrorResp(w, err, http.StatusNotFound, loggerdataresp.PostgresNoRowsErrorCheck) {
				continue
			}
			args.KeyBase64 = base64.StdEncoding.EncodeToString(symKeys.SecOC.Bytes())
			SendToVin(vin, args)
		}
		utils.RespJSON(w, http.StatusOK, &common.JSONMessage{
			Message: fmt.Sprintf("remote diagnostic command sent to %d vehicles", len(request.VINs)),
		})
		return
	}

	args, err := parseRequest(request, w)
	if err != nil {// TODO: need to write an error to request
		return
	}
	
	for _, vin := range request.VINs {
		SendToVin(vin, args)
	}

	utils.RespJSON(w, http.StatusOK, &common.JSONMessage{
		Message: fmt.Sprintf("remote diagnostic command sent to %d vehicles", len(request.VINs)),
	})
}
