package handlers

import (
	"context"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common"
	orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
	"github.com/fiskerinc/cloud-services/pkg/mongo"
	"github.com/fiskerinc/cloud-services/pkg/utils"
	"github.com/fiskerinc/cloud-services/pkg/validator"
	"github.com/julienschmidt/httprouter"
	"github.com/pkg/errors"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo/options"

	"otaupdate/services"

	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
)

// HandleECUDTCGet godoc
// @Summary Get ECU DTCs for a specific vehicle
// @Description Get ECU diagnostic trouble codes (DTCs) for a specific vehicle within a given time range
// @Tags ECU
// @Accept  json
// @Produce  json
// @Param Authorization header string false "Bearer <ID token>"
// @Param Api-Key header string false "<API token>"
// @Param vin path string true "VIN"
// @Param ecu query string false "ECU"
// @Param trouble_code query string false "Trouble Code"
// @Param start_time query string false "Start time (RFC3339 format)"
// @Param end_time query string false "End time (RFC3339 format)"
// @Param limit query int false "Max number of records"
// @Param offset query int false "Records offset"
// @Param order query string false "Sort on column with asc or desc"
// @Param decode query bool false "Return decoded dtc information"
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.DTC_ECU} "List of DTC ECU data"
// @Failure 400 {object} common.JSONError "Bad request"
// @Failure 401 {object} common.JSONError "Unauthorized"
// @Failure 404 {object} common.JSONError "Not found"
// @Failure 503 {object} common.JSONError "Service unavailable"
// @Router /dtcs/{vin} [get]
func HandleECUDTCGet(w http.ResponseWriter, r *http.Request) {
	params := httprouter.ParamsFromContext(r.Context())
	vin := params.ByName("vin")

	queryParams := r.URL.Query()
	ecu := queryParams.Get("ecu")
	troubleCode := queryParams.Get("trouble_code")
	startStr := queryParams.Get("start_time")
	endStr := queryParams.Get("end_time")

	decode, _ := strconv.ParseBool(queryParams.Get("decode"))
	filter := bson.M{
		"vin": vin}
	if ecu != "" {
		filter["ecu"] = ecu
	}
	if troubleCode != "" {
		troubleCodeInt, err := strconv.ParseInt(troubleCode, 10, 64)
		if err != nil {
			http.Error(w, "Invalid trouble_code format, use int64", http.StatusBadRequest)
			return
		}
		filter["dtc"] = troubleCodeInt
	}

	err := validator.GetValidator().Var(vin, "vin|vinsuffix")
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}
	var start time.Time
	if startStr != "" {
		start, err = time.Parse(time.RFC3339, startStr)
		if err != nil {
			http.Error(w, "Invalid start_time format, use RFC3339 format", http.StatusBadRequest)
			return
		}
	}
	var end time.Time

	if endStr != "" {
		end, err = time.Parse(time.RFC3339, endStr)
		if err != nil {
			http.Error(w, "Invalid end_time format, use RFC3339 format", http.StatusBadRequest)
			return
		}

	}
	if !start.IsZero() && !end.IsZero() {
		filter["created_at"] = bson.M{
			"$gte": start,
			"$lte": end,
		}
	} else if !start.IsZero() {
		filter["created_at"] = bson.M{
			"$gte": start,
		}
	} else if !end.IsZero() {
		filter["created_at"] = bson.M{
			"$lte": end,
		}
	}

	mongoOpts := options.Find()

	query_params, err := orm.ParsePageQuery(r)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}
	mongoOpts.SetLimit(int64(query_params.Limit))

	if query_params.Order != "" {
		mongoOpts.SetSort(bson.D{
			{"created_at", -1}}) // Descending order for 'created_at'.
	}

	if query_params.Offset != 0 {
		mongoOpts.SetSkip(int64(query_params.Offset))
	}

	mongo, err := services.GetMongoClient()

	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	var total int64
	if query_params.Offset == 0 {
		total, err = mongo.Collection("dtcs").CountDocuments(ctx, filter)
		if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable, loggerdataresp.PostgresNoRowsErrorCheck) {
			return
		}
	}

	cursor, err := mongo.Collection("dtcs").Find(ctx, filter, mongoOpts)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}

	var dtcs []common.DTC_ECU

	for cursor.Next(ctx) {
		var result common.DTC_ECU
		err := cursor.Decode(&result)
		if err != nil {
			logger.Warn().Msg(err.Error())
			continue
		}
		if decode {
			fetchDTCDataFromMongo(&result)
		}
		dtcs = append(dtcs, result)
	}

	utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{
		Data:  dtcs,
		Total: int(total),
	})
}

func fetchDTCDataFromMongo(dtc *common.DTC_ECU) (err error) {
	client, err := mongo.GetPDXMongoClient()
	if err != nil {
		err = errors.WithStack(err)
		return
	}

	// Handle the dtc string, need to drop the first byte as its the status code
	troubleCodeHex := fmt.Sprintf("%X", dtc.TroubleCode)
	troubleCodeHex = strings.ToUpper(troubleCodeHex)
	info, err := client.GetDTCDefinitionByHexString(troubleCodeHex, dtc.ECU)
	if err != nil {
		return
	}

	if info == nil {
		logger.Warn().Msgf("Failed to find dtc code from ecu: %s troubleCodeHex: %s", dtc.ECU, troubleCodeHex)
	}

	dtc.Information = info
	dtc.StatusByteDecode = dtc.DTCStatusByteMeaning()
	return
}
