package handlers

import (
	"fmt"
	"io"
	"math"
	"net/http"
	"otaupdate/services"
	"strings"

	"github.com/fiskerinc/cloud-services/pkg/clickhouse"
	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils"
	"github.com/fiskerinc/cloud-services/pkg/validator"
	"github.com/gorilla/schema"
	"github.com/intel-go/fastjson"
	"github.com/pkg/errors"
	"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
)

// HandleVehiclePathsPost godoc
// @Summary Gets paths of vehicles
// @Description Returns vehicle paths
// @Accept  json
// @Produce  json
// @Param Authorization header string false "Bearer <ID token>"
// @Param Api-Key header string false "<API token>"
// @Param vins body vehicleBodyParams true "List of VINs"
// @Param lookback_hours query int false "Data lookback window in hours. Default is 24 if not set"
// @Success 200 {object} common.GpsPaths
// @Failure 400 {object} common.JSONError "Bad request"
// @Failure 401 {object} common.JSONError "Unauthorized"
// @Failure 503 {object} common.JSONError "Service unavailable"
// @Router /vehicle_paths [post]
func HandleVehiclePathsPost(w http.ResponseWriter, r *http.Request) {
	query, body, err := parseVehiclePathsParams(r)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}

	conn, err := services.GetClickhouseClient()
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
		logger.Error().Err(err).Msg("cannot get clickhouse client")
		return
	}

	paths, err := selectVehiclePaths(conn, body.VINs, query.LookbackHours)

	if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
		logger.Error().Err(err).Msg("failed to select vehicle paths from feature_table")
		return
	}

	utils.RespJSON(w, http.StatusOK, paths)
}

func parseVehiclePathsParams(r *http.Request) (vehiclePathQuery, vehicleBodyParams, error) {
	sch := schema.NewDecoder()
	sch.SetAliasTag("json")
	queryParams := vehiclePathQuery{}
	bodyParams := vehicleBodyParams{}

	//process query params
	err := sch.Decode(&queryParams, r.URL.Query())
	if err != nil {
		return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
	}

	err = validator.GetValidator().Struct(queryParams)
	if err != nil {
		return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
	}

	//process body params
	err = fastjson.NewDecoder(r.Body).Decode(&bodyParams)

	if err != nil && err != io.EOF {
		return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
	}

	err = validator.GetValidator().Struct(bodyParams)
	if err != nil {
		return vehiclePathQuery{}, vehicleBodyParams{}, errors.WithStack(err)
	}

	//set default value for queryParams
	if queryParams.LookbackHours == 0 {
		queryParams.LookbackHours = 24
	}

	return queryParams, bodyParams, nil
}

func selectVehiclePaths(conn clickhouse.ClientInterface, vinList []string, lookbackHours int64) (common.GpsPaths, error) {
	var vinListSb strings.Builder
	//serialize vinList for query
	vinListSb.WriteRune('[')
	for i, vin := range vinList {
		vinListSb.WriteRune('\'')
		vinListSb.WriteString(vin)
		vinListSb.WriteRune('\'')

		if i < len(vinList)-1 {
			vinListSb.WriteRune(',')
		}
	}
	vinListSb.WriteRune(']')

	//query gps routes
	queryText := fmt.Sprintf("SELECT VIN, TBOX_GPSLati, TBOX_GPSLongi FROM feature_table WHERE Timestamp > now() - (interval %d hour) AND VIN in %s ORDER BY Timestamp ASC",
		lookbackHours, vinListSb.String())

	logger.Info().Msgf("query: %s", queryText)

	var results []ChGpsRow
	err := conn.Select(&results, queryText)
	if err != nil {
		return nil, err
	}

	//transform query results
	var pathsMap common.GpsPaths = make(common.GpsPaths, len(vinList))

	//populate pathsMap
	for _, vin := range vinList {
		pathsMap[vin] = common.GpsPath{}
	}

	for _, row := range results {
		_, ok := pathsMap[row.VIN]
		if !ok {
			pathsMap[row.VIN] = common.GpsPath{}
		}

		//ignore invalid GPS points
		if math.IsNaN(row.TBOX_GPSLati) || math.IsNaN(row.TBOX_GPSLongi) {
			continue
		}
		if row.TBOX_GPSLati == 0 && row.TBOX_GPSLongi == 0 {
			continue
		}
		if row.TBOX_GPSLati > 90 || row.TBOX_GPSLati < -90 {
			continue
		}
		if row.TBOX_GPSLongi > 180 || row.TBOX_GPSLongi < -180 {
			continue
		}

		pathsMap[row.VIN] = append(pathsMap[row.VIN],
			common.GpsPoint{row.TBOX_GPSLati, row.TBOX_GPSLongi},
		)
	}

	return pathsMap, nil
}

type ChGpsRow struct {
	VIN           string  `ch:"VIN" json:"VIN"`
	TBOX_GPSLati  float64 `ch:"TBOX_GPSLati" json:"TBOX_GPSLati"`
	TBOX_GPSLongi float64 `ch:"TBOX_GPSLongi" json:"TBOX_GPSLongi"`
}

type vehiclePathQuery struct {
	LookbackHours int64 `json:"lookback_hours"`
}

type vehicleBodyParams struct {
	VINs []string `json:"vins" validate:"required"`
}
