package mqtt_client

import (
	"encoding/json"
	"log"
	"time"

	"github.com/AFASystems/presence/internal/pkg/model"
	"github.com/AFASystems/presence/internal/pkg/persistence"
	"github.com/yosssi/gmq/mqtt"
	"github.com/yosssi/gmq/mqtt/client"
)

func getLikelyLocations(settings *model.Settings, ctx *model.AppContext, cl *client.Client) {
	ctx.HTTPResults.HTTPResultsLock.Lock()
	defer ctx.HTTPResults.HTTPResultsLock.Unlock()
	ctx.HTTPResults.HTTPResults = model.HTTPLocationsList{Beacons: []model.HTTPLocation{}}

	shouldPersist := false

	for id, beacon := range ctx.Beacons.Beacons {
		if len(beacon.Beacon_metrics) == 0 {
			continue
		}

		if isExpired(&beacon, settings) {
			handleExpiredBeacon(&beacon, cl, ctx)
			continue
		}

		best := calculateBestLocation(&beacon)
		updateBeaconState(&beacon, best, settings, ctx, cl)

		appendHTTPResult(ctx, beacon, best)
		ctx.Beacons.Beacons[id] = beacon
		shouldPersist = true
	}

	if shouldPersist {
		persistence.PersistBeacons(&ctx.Beacons)
	}
}

func isExpired(b *model.Beacon, s *model.Settings) bool {
	return time.Now().Unix()-b.Beacon_metrics[len(b.Beacon_metrics)-1].Timestamp > s.Last_seen_threshold
}

func handleExpiredBeacon(b *model.Beacon, cl *client.Client, ctx *model.AppContext) {
	if b.Expired_location == "expired" {
		return
	}
	b.Expired_location = "expired"
	msg := model.Message{
		Email:    b.Previous_confident_location,
		Username: b.Name,
		Message:  "expired",
	}
	data, _ := json.Marshal(msg)
	log.Println(string(data))
	ctx.Broadcast <- msg
}

func calculateBestLocation(b *model.Beacon) model.BestLocation {
	locScores := map[string]float64{}
	for _, m := range b.Beacon_metrics {
		score := 1.5 + 0.75*(1.0-(float64(m.Rssi)/-100.0))
		locScores[m.Location] += score
	}
	bestName, bestScore := "", 0.0
	for name, score := range locScores {
		if score > bestScore {
			bestName, bestScore = name, score
		}
	}
	last := b.Beacon_metrics[len(b.Beacon_metrics)-1]
	return model.BestLocation{Name: bestName, Distance: last.Distance, Last_seen: last.Timestamp}
}

func updateBeaconState(b *model.Beacon, best model.BestLocation, s *model.Settings, ctx *model.AppContext, cl *client.Client) {
	updateLocationHistory(b, best.Name)
	updateConfidence(b, best.Name, s)

	if locationChanged(b, best, s) {
		publishLocationChange(b, best, cl)
		b.Location_confidence = 0
		b.Previous_confident_location = best.Name
	}
}

func updateLocationHistory(b *model.Beacon, loc string) {
	b.Location_history = append(b.Location_history, loc)
	if len(b.Location_history) > 10 {
		b.Location_history = b.Location_history[1:]
	}
}

func updateConfidence(b *model.Beacon, loc string, s *model.Settings) {
	counts := map[string]int{}
	for _, l := range b.Location_history {
		counts[l]++
	}

	maxCount, mostCommon := 0, ""
	for l, c := range counts {
		if c > maxCount {
			maxCount, mostCommon = c, l
		}
	}

	if maxCount >= 7 {
		if mostCommon == b.Previous_confident_location {
			b.Location_confidence++
		} else {
			b.Location_confidence = 1
			b.Previous_confident_location = mostCommon
		}
	}
}

func locationChanged(b *model.Beacon, best model.BestLocation, s *model.Settings) bool {
	return (b.Location_confidence == s.Location_confidence &&
		b.Previous_confident_location != best.Name) ||
		b.Expired_location == "expired"
}

func publishLocationChange(b *model.Beacon, best model.BestLocation, cl *client.Client) {
	location := best.Name
	if b.Expired_location == "expired" {
		location = "expired"
	}

	js, err := json.Marshal(model.LocationChange{
		Beacon_ref:        *b,
		Name:              b.Name,
		Previous_location: b.Previous_confident_location,
		New_location:      location,
		Timestamp:         time.Now().Unix(),
	})
	if err != nil {
		return
	}

	err = cl.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS1,
		TopicName: []byte("afa-systems/presence/changes"),
		Message:   js,
	})
	if err != nil {
		log.Printf("mqtt publish error: %v", err)
	}
}

func appendHTTPResult(ctx *model.AppContext, b model.Beacon, best model.BestLocation) {
	ctx.HTTPResults.HTTPResultsLock.Lock()
	defer ctx.HTTPResults.HTTPResultsLock.Unlock()

	r := model.HTTPLocation{
		Name:      b.Name,
		Beacon_id: b.Beacon_id,
		Location:  best.Name,
		Distance:  best.Distance,
		Last_seen: best.Last_seen,
	}
	ctx.HTTPResults.HTTPResults.Beacons = append(ctx.HTTPResults.HTTPResults.Beacons, r)
}
