package eventbus

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	amqp "github.com/rabbitmq/amqp091-go"
	"go.uber.org/zap"
)

// Event représente un événement métier dans le système
// Suit le pattern défini dans ORIGIN_MASTER_ARCHITECTURE.md
type Event struct {
	EventID       string                 `json:"event_id"`
	EventType     string                 `json:"event_type"` // format: {domain}.{entity}.{action}.{version}
	AggregateID   string                 `json:"aggregate_id"`
	AggregateType string                 `json:"aggregate_type"`
	Timestamp     time.Time              `json:"timestamp"`
	Version       int                    `json:"version"`
	Data          map[string]interface{} `json:"data"`
	Metadata      map[string]interface{} `json:"metadata"`
}

// RabbitMQClient gère la connexion et publication d'événements vers RabbitMQ
// Implémentation minimale alignée avec ORIGIN pour Phase 1
type RabbitMQClient struct {
	conn     *amqp.Connection
	channel  *amqp.Channel
	exchange string
	logger   *zap.Logger
}

// NewRabbitMQClient crée un nouveau client RabbitMQ
// url format: amqp://user:pass@host:5672/
func NewRabbitMQClient(url, exchange string, logger *zap.Logger) (*RabbitMQClient, error) {
	conn, err := amqp.Dial(url)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to RabbitMQ: %w", err)
	}

	channel, err := conn.Channel()
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("failed to open channel: %w", err)
	}

	// Déclarer l'exchange (topic type pour routing flexible)
	err = channel.ExchangeDeclare(
		exchange, // name
		"topic",  // type
		true,     // durable
		false,    // auto-deleted
		false,    // internal
		false,    // no-wait
		nil,      // arguments
	)
	if err != nil {
		channel.Close()
		conn.Close()
		return nil, fmt.Errorf("failed to declare exchange: %w", err)
	}

	logger.Info("RabbitMQ client initialized",
		zap.String("exchange", exchange),
		zap.String("url", url),
	)

	return &RabbitMQClient{
		conn:     conn,
		channel:  channel,
		exchange: exchange,
		logger:   logger,
	}, nil
}

// PublishEvent publie un événement sur RabbitMQ
// routingKey format: {domain}.{entity}.{action} (ex: "auth.user.registered")
func (c *RabbitMQClient) PublishEvent(ctx context.Context, event *Event) error {
	body, err := json.Marshal(event)
	if err != nil {
		return fmt.Errorf("failed to marshal event: %w", err)
	}

	err = c.channel.PublishWithContext(
		ctx,
		c.exchange,      // exchange
		event.EventType, // routing key
		false,           // mandatory
		false,           // immediate
		amqp.Publishing{
			ContentType:  "application/json",
			DeliveryMode: amqp.Persistent, // messages persistent
			Timestamp:    event.Timestamp,
			MessageId:    event.EventID,
			Type:         event.EventType,
			Body:         body,
		},
	)

	if err != nil {
		c.logger.Error("Failed to publish event",
			zap.Error(err),
			zap.String("event_type", event.EventType),
			zap.String("event_id", event.EventID),
		)
		return fmt.Errorf("failed to publish event: %w", err)
	}

	c.logger.Debug("Event published",
		zap.String("event_type", event.EventType),
		zap.String("event_id", event.EventID),
		zap.String("aggregate_id", event.AggregateID),
	)

	return nil
}

// Close ferme proprement la connexion RabbitMQ
func (c *RabbitMQClient) Close() error {
	if c.channel != nil {
		c.channel.Close()
	}
	if c.conn != nil {
		c.conn.Close()
	}
	c.logger.Info("RabbitMQ client closed")
	return nil
}

// HealthCheck vérifie si la connexion RabbitMQ est active
func (c *RabbitMQClient) HealthCheck() error {
	if c.conn == nil || c.conn.IsClosed() {
		return fmt.Errorf("RabbitMQ connection is closed")
	}
	return nil
}
