package handlers

import (
	"errors"
	"net/http"
	"otaupdate/services"
	"sort"
	"strconv"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/httphandlers"
	"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
	"github.com/fiskerinc/cloud-services/pkg/utils"
	"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
	"github.com/fiskerinc/cloud-services/pkg/validator"
)

var apiCreateToken string = envtool.GetEnv("MIGRATE_CREATE_TOKEN", "")

// HandleFlashpackVersionAdd godoc
// @Summary Add a flashpack version
// @Description Add a flashpack version
// @Accept  json
// @Produce  json
// @Param Authorization header string false "Bearer <ID token>"
// @Param Api-Key header string false "<API token>"
// @Param data body common.CarFlashpackVersionAddRequest true "Mappings between ECU versions and a flashpack number"
// @Success 200 {object} common.JSONDBQueryResult "Created flashpack ecu mapping result"
// @Failure 400 {object} common.JSONError "Bad request"
// @Failure 401 {object} common.JSONError "Unauthorized"
// @Failure 503 {object} common.JSONError "Service unavailable"
// @Router /flashpack_version [post]
func HandleFlashpackVersionAdd(w http.ResponseWriter, r *http.Request) {
	var req common.CarFlashpackVersionAddRequest

	err := httphandlers.ParseRequest(r, &req)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}

	if len(req.ECUVersions) < 1 {
		loggerdataresp.BadDataErrorResp(w, errors.New("CarECUName and CarECUVersion required"), http.StatusBadRequest)
		return
	}

	// Include previous flashpack mappings
	previousMappings, err := services.GetDB().GetCars().GetCarFlashpackVersionMappingsByModelTrim(req.CarModel, req.CarTrim, nil)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
		return
	}

	// the flashpacks are stored in the database as strings, so we have to sort them numerically
	// in descending order by year and flashpack
	sort.Slice(previousMappings, func(i, j int) bool {
		iFp, _ := strconv.ParseFloat(previousMappings[i].Flashpack, 64) // guaranteed numeric
		jFp, _ := strconv.ParseFloat(previousMappings[j].Flashpack, 64) // guaranteed numeric
		iYear := previousMappings[i].CarYear
		jYear := previousMappings[j].CarYear

		if iYear == jYear {
			return iFp > jFp
		} else {
			return iYear > jYear
		}
	})

	// Put all the mappings into one array, still in descending order by flashpack number
	// only include the ones that are less than or equal to the new flashpack number being added
	mappings := []common.CarFlashpackVersion{}
	for _, v := range req.ECUVersions {
		mappings = append(mappings, common.CarFlashpackVersion{
			CarECUName:    v.CarECUName,
			CarECUVersion: v.CarECUVersion,
			Flashpack:     req.Flashpack,
			CarModel:      req.CarModel,
			CarTrim:       req.CarTrim,
			CarYear:       req.CarYear,
		})
	}
	for _, m := range previousMappings {
		reqFp, _ := strconv.ParseFloat(req.Flashpack, 64) // already validated as numeric
		mFp, _ := strconv.ParseFloat(m.Flashpack, 64)     // already validated as numeric
		if (m.CarYear < req.CarYear) ||
			(m.CarYear == req.CarYear && mFp <= reqFp) {
			mappings = append(mappings, m)
		}
	}

	// Put the mappings in a map by ecu name
	// There can be more than one ECU version for an ECU for a flashpack
	var newMappings = make(map[string][]common.CarFlashpackVersion)
	for _, m := range mappings {
		// Only include the mapping if it is one of the latest
		latestVersionMappings, ok := newMappings[m.CarECUName]
		// Include multiple versions for the same ecu and flashpack number
		if (ok && m.Flashpack == latestVersionMappings[0].Flashpack) || !ok {
			newMappings[m.CarECUName] = append(newMappings[m.CarECUName], m)
		}
	}

	// Flatten the map into an array
	var newMappingsArray []common.CarFlashpackVersion
	for _, m := range newMappings {
		newMappingsArray = append(newMappingsArray, m...)
	}

	// Apply the new flashpack number to all the mappings to be inserted
	for i := range newMappingsArray {
		newMappingsArray[i].Flashpack = req.Flashpack
		newMappingsArray[i].CarYear = req.CarYear
	}

	err = services.GetDB().GetCars().AddCarFlashpackVersionMappings(newMappingsArray)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
		return
	}

	// Also add to other environments, as required
	for _, targetURL := range targetURLS {
		if !validator.ValidateURL(targetURL) || apiCreateToken == "" {
			break // No URL in MANIFEST_MIGRATE_URLS
		}

		otaService := services.NewOtaService(targetURL, apiCreateToken)

		resp, err := otaService.FlashpackVersionAdd(req)
		if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
			return
		}

		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			utils.ForwardResponse(w, resp)
		}
	}

	utils.RespJSON(w, http.StatusOK, common.JSONMessage{
		Message: "Created",
	})
}
