// Package mq exposes the broker-agnostic messaging seam used by services.
package mq

import (
	"context"
	"encoding/json"
	"errors"
	"time"
)

var ErrUnavailable = errors.New("message broker unavailable")

const (
	ProviderEMQX     = "emqx"
	ProviderRabbitMQ = "rabbitmq"
)

const (
	AtMostOnce  byte = 0
	AtLeastOnce byte = 1
	ExactlyOnce byte = 2
)

type Message struct {
	Topic   string
	Payload []byte
	QoS     byte
	Retain  bool
}

func (m Message) DecodeJSON(target any) error { return json.Unmarshal(m.Payload, target) }

type Handler func(context.Context, Message)

// TopicSubscription is one logical module subscription. The handler is kept
// by the runtime and is replayed after a client reconnects or is rebuilt.
type TopicSubscription struct {
	Topic   string
	QoS     byte
	Handler Handler
}

// SubscriptionSet is the complete subscription declaration for one module and
// provider. Registering the same owner/provider replaces its previous set.
// Owner must be a stable module name, not a request or goroutine identifier.
type SubscriptionSet struct {
	Owner    string
	Provider string
	Topics   []TopicSubscription
}

// SubscriptionRegistrar is the module-facing seam for durable-in-process
// subscription intent. Register does not require a live broker; the runtime
// will bind the declared topics when the provider becomes available.
type SubscriptionRegistrar interface {
	Register(SubscriptionSet) error
	Unregister(owner string) error
}

// SubscriptionContributor lets a module expose its broker subscriptions
// without depending on the concrete integration implementation.
type SubscriptionContributor interface {
	RegisterSubscriptions(SubscriptionRegistrar) error
}

type Client interface {
	Publish(context.Context, string, []byte, byte, bool) error
	Subscribe(context.Context, string, byte, Handler) error
	Unsubscribe(context.Context, ...string) error
	Connected() bool
	Close() error
}

// Registry exposes named broker clients while preserving Client as the
// default EMQX/MQTT boundary for existing modules.
type Registry interface {
	SubscriptionRegistrar
	Client(provider string) Client
	PublishTo(context.Context, string, string, []byte, byte, bool) error
	SubscribeTo(context.Context, string, string, byte, Handler) error
	UnsubscribeFrom(context.Context, string, ...string) error
	ConnectedTo(provider string) bool
}

type Config struct {
	Enabled           bool
	Broker            string
	ClientID          string
	Username          string
	Password          string
	KeepAlive         time.Duration
	CleanSession      bool
	ConnectTimeout    time.Duration
	ReconnectInterval time.Duration
}

func PublishJSON(ctx context.Context, client Client, topic string, value any, qos byte, retain bool) error {
	if client == nil {
		return ErrUnavailable
	}
	payload, err := json.Marshal(value)
	if err != nil {
		return err
	}
	return client.Publish(ctx, topic, payload, qos, retain)
}
