package mqtt

import (
	"fmt"
	"time"

	paho "github.com/eclipse/paho.mqtt.golang"
)

type MessageHandler func(topic string, payload []byte, qos byte, retained bool)

type Manager struct {
	client   paho.Client
	handler  MessageHandler
	subTopic string
	subQOS   byte
	sysTopic string
}

func NewManager(broker, username, password, clientID string, handler MessageHandler, subTopic string, subQOS byte, sysTopic string) (*Manager, error) {
	opts := paho.NewClientOptions()
	opts.AddBroker(broker)
	opts.SetClientID(clientID)
	opts.SetAutoReconnect(true)
	opts.SetConnectRetry(true)
	opts.SetConnectRetryInterval(5 * time.Second)
	if username != "" {
		opts.SetUsername(username)
		opts.SetPassword(password)
	}

	mgr := &Manager{
		handler:  handler,
		subTopic: subTopic,
		subQOS:   subQOS,
		sysTopic: sysTopic,
	}

	opts.SetOnConnectHandler(func(c paho.Client) {
		if token := c.Subscribe(mgr.subTopic, mgr.subQOS, mgr.onMessage); token.Wait() && token.Error() != nil {
			fmt.Printf("erreur subscribe MQTT: %v\n", token.Error())
		}
		if mgr.sysTopic != "" {
			if token := c.Subscribe(mgr.sysTopic, mgr.subQOS, mgr.onMessage); token.Wait() && token.Error() != nil {
				fmt.Printf("erreur subscribe SYS MQTT: %v\n", token.Error())
			}
		}
	})

	opts.SetConnectionLostHandler(func(_ paho.Client, err error) {
		fmt.Printf("connexion MQTT perdue: %v\n", err)
	})

	mgr.client = paho.NewClient(opts)
	if token := mgr.client.Connect(); token.Wait() && token.Error() != nil {
		return nil, fmt.Errorf("connexion MQTT: %w", token.Error())
	}

	return mgr, nil
}

func (m *Manager) onMessage(_ paho.Client, msg paho.Message) {
	if m.handler == nil {
		return
	}
	m.handler(msg.Topic(), msg.Payload(), msg.Qos(), msg.Retained())
}

func (m *Manager) Publish(topic string, payload []byte, qos byte, retained bool) error {
	if m.client == nil {
		return fmt.Errorf("client MQTT indisponible")
	}
	token := m.client.Publish(topic, qos, retained, payload)
	token.Wait()
	return token.Error()
}

func (m *Manager) Disconnect() {
	if m.client != nil && m.client.IsConnected() {
		m.client.Disconnect(250)
	}
}
