package main

import (
	"encoding/json"
	"log/slog"
	"slices"

	mqtt "github.com/eclipse/paho.mqtt.golang"
	"gitlab.imt-atlantique.fr/xaal/code/go/core/xaal"
)

type Gateway struct {
	Client  mqtt.Client
	Engine  *xaal.Engine
	Devices map[string]*Z2MDevice
	Config  *Config
}

func NewGW(cfg *Config, eng *xaal.Engine) *Gateway {
	gw := &Gateway{Devices: make(map[string]*Z2MDevice)}
	gw.Config = cfg
	gw.Engine = eng
	gw.Client = MQTTSetup(cfg, gw.MQTTHandler)
	return gw
}

func (gw *Gateway) GetZDevice(name string) *Z2MDevice {
	return gw.Devices[name]
}

func (gw *Gateway) AddZDevice(zDev *Z2MDevice) {
	for _, xDev := range zDev.XAALDevices {
		dev := xDev.GetXAALDevice()
		slog.Debug("Adding device", "dev", dev.String())
		gw.Engine.AddDevice(dev)
	}
	zDev.Gateway = gw
	gw.Devices[zDev.FriendlyName] = zDev
}

func (gw *Gateway) RemoveZDevice(zDev *Z2MDevice) {
	for _, xDev := range zDev.XAALDevices {
		dev := xDev.GetXAALDevice()
		slog.Debug("Removing device", "dev", dev.String())
		gw.Engine.RemoveDevice(dev)
	}
	zDev.Gateway = nil
	delete(gw.Devices, zDev.FriendlyName)
}

func (gw *Gateway) GetZDevices() map[string]*Z2MDevice {
	return gw.Devices
}

func (gw *Gateway) GetZDeviceByTopic(topic string) *Z2MDevice {
	name := topic[len(gw.Config.topic+"/"):]
	return gw.GetZDevice(name)
}

// MQTTHandler handles all incoming MQTT messages
// If the topic is /bridge/devices it will parse the json and create new devices
// Else it will find the device with the topic and call the HandleMessage
func (gw *Gateway) MQTTHandler(client mqtt.Client, msg mqtt.Message) {
	// some messages are empty
	if len(msg.Payload()) == 0 {
		return
	}
	// we ignore some topics
	if slices.Contains(gw.Config.ignoredTopics, msg.Topic()) {
		return
	}
	// Is it devices definitions ?
	if msg.Topic() == gw.Config.topic+"/bridge/devices" {
		gw.Z2MParseDevices(msg)
	} else {
		zDev := gw.GetZDeviceByTopic(msg.Topic())
		if zDev != nil {
			zDev.HandleMessage(msg)
		}
	}
}

// Z2MParseDevices parses the bridge/devices json and and sync xAAL with results
func (gw *Gateway) Z2MParseDevices(msg mqtt.Message) {
	var devices []Z2MDevice
	slog.Debug("Parsing devices")
	err := json.Unmarshal(msg.Payload(), &devices)
	if err != nil {
		slog.Error("Error decoding JSON", "err", err)
		return
	}

	// search for new devices
	for _, zDev := range devices {
		known := gw.GetZDevice(zDev.FriendlyName)
		if known != nil {
			continue
		}
		slog.Debug("Found new device", "name", zDev.FriendlyName)
		zDev.Dump()
		zDev.FindXAALDevices(gw)
		gw.AddZDevice(&zDev)
		zDev.Sync()
		// zDev.Available()
	}
	// search for deleted devices
	for _, zDev := range gw.GetZDevices() {
		found := false
		for _, dev := range devices {
			if dev.FriendlyName == zDev.FriendlyName {
				found = true
				break
			}
		}
		if !found {
			slog.Debug("Device removed", "name", zDev.FriendlyName)
			gw.RemoveZDevice(zDev)
		}
	}
}