package mongo

import (
	"context"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/pkg/errors"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

type DTCCollectionInterface interface {
	GetDTCDefinitionByHexString(troubleCodeHex string) (info common.DTCInformation, err error)
	GetLatestPDXVersions() (latestVersion PDXVersion, err error)
}

var (
	pdxMongodbOnce     sync.Once
	pdxMongodbInstance *DTCCollection
)

type DTCCollection struct {
	CollectionInterface
	Client *mongo.Client
	DB     *mongo.Database
}

func GetPDXMongoClient() (*DTCCollection, error) {
	var err error
	pdxMongodbOnce.Do(func() {
		logger.Info().Msg("init MongoDB instance")
		pdxMongodbInstance = &DTCCollection{}
		var cl *mongo.Client
		var db *mongo.Database
		cl, db, err = NewMongoConnection(ODXDB)

		pdxMongodbInstance.Client = cl
		pdxMongodbInstance.DB = db
	})
	return pdxMongodbInstance, err
}

// Given a Hex String for 3 bytes, get the dtc information from mongo
func (coll *DTCCollection) GetDTCDefinitionByHexString(troubleCodeHex string, ecuSource string) (info *common.DTCInformation, err error) {
	if replacement, exist := common.CarDTCLookupInverse[ecuSource]; exist {
		ecuSource = replacement
	} 

	collect := coll.DB.Collection("dtc_lookup")
	//db.dtc_lookup.aggregate({"$match": {"ecuName": "ECU", "DtcUniqueId": "V2.6.0"}}, {"$unwind": "$dtcData.DtcList"}, {"$match": {"dtcData.DtcList.TroubleCodeHex":"800156"}}, {"$project":{"_id": 0, "Obj": "$dtcData.DtcList"}})
	pdxVersion, err := coll.GetLatestPDXVersions()
	if err != nil {
		return
	}
	a := bson.D{}
	if ecuSource == "AMP"  {
		a = bson.D{{"$match", bson.D{{"ecuName", bson.M{"$in": []string{"AMPSO", "AMPHA"}}}, {"DtcUniqueId", pdxVersion.PDXVersion}}}}
	} else {
		a = bson.D{{"$match", bson.D{{"ecuName", ecuSource}, {"DtcUniqueId", pdxVersion.PDXVersion}}}}
	}

	b := bson.D{{"$unwind", "$dtcData.DtcList"}}
	c := bson.D{{"$match", bson.D{{"dtcData.DtcList.TroubleCodeHex", troubleCodeHex}}}}
	d := bson.D{{"$project", bson.D{{"_id", 0}, {"Obj", "$dtcData.DtcList"}}}}
	cursor, err := collect.Aggregate(context.Background(), mongo.Pipeline{a, b, c, d})
	if err != nil {
		err = errors.WithStack(err)
		logger.Err(err).Msg("Failed to call aggregate in GetDTCDefinitionByHexString")
		return
	}

	var obj DTCOuterObj
	for cursor.Next(context.Background()) {
		err = cursor.Decode(&obj)
		if err != nil {
			err = errors.WithStack(err)
			logger.Err(err).Msg("Failed to decode mongo object into DTCInformation")
			return
		}
		info = &obj.DTCInformation
	}
	return
}

// This value should be cached for a period of time
func (coll *DTCCollection) GetLatestPDXVersions() (latestVersion PDXVersion, err error) {
	//db.vod_lookup.aggregate({"$project": {"VodUniqueId": 1, "createDate": 1, "updateDate": 1}})
	collect := coll.DB.Collection("vod_lookup")
	a := bson.D{{"$project", bson.D{{"VodUniqueId", 1}, {"createDate", 1}, {"updateDate", 1}, {"_id", 0}}}}
	cursor, err := collect.Aggregate(context.Background(), mongo.Pipeline{a})
	if err != nil {
		err = errors.WithStack(err)
		logger.Err(err).Msg("")
		return
	}

	// Need to determine the latest version
	for cursor.Next(context.Background()) {
		var ag PDXVersion
		err = cursor.Decode(&ag)
		if err != nil {
			err = errors.WithStack(err)
			logger.Err(err).Msg("")
			return
		}
		if isVersionNewer(latestVersion.PDXVersion, ag.PDXVersion) {
			latestVersion = ag
		}
	}
	return
}

// Give the one we have as the newest, and then the new value to compare to
func isVersionNewer(lastNewest, newPossibleNewest string) (isNewer bool) {
	// Better algorithm, turn V1.3.2.1 => 1321 and add any 0's on the end if the other number is longer
	if lastNewest == "" {
		return true
	}
	// Remove the V
	lastNewest = lastNewest[1:]
	newPossibleNewest = newPossibleNewest[1:]
	last := strings.Split(lastNewest, ".")
	new := strings.Split(newPossibleNewest, ".")
	for x := 0; x < len(last); x++ {
		// We have gotten to the end of the second string without being less, so we are greater, like a hotfix
		if x >= len(new) {
			return false
		}
		l, _ := strconv.Atoi(last[x])
		n, _ := strconv.Atoi(new[x])

		if n > l {
			return true
		} else if l > n {
			return false
		}
	}
	return len(new) > len(last)
}

type DTCOuterObj struct {
	common.DTCInformation `bson:"Obj"`
}

type PDXVersion struct {
	PDXVersion  string    `bson:"VodUniqueId"`
	CreatedTime time.Time `bson:"createDate"`
	UpdatedTime time.Time `bson:"updateDate"`
}
