package rabbitmq

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

	amqp "github.com/rabbitmq/amqp091-go"
	"github.com/rwadurian/mpc-system/pkg/logger"
	"github.com/rwadurian/mpc-system/services/session-coordinator/application/ports/output"
	"go.uber.org/zap"
)

// EventPublisherAdapter implements MessageBrokerPort using RabbitMQ
type EventPublisherAdapter struct {
	conn    *amqp.Connection
	channel *amqp.Channel
	mu      sync.Mutex
}

// NewEventPublisherAdapter creates a new RabbitMQ event publisher
func NewEventPublisherAdapter(conn *amqp.Connection) (*EventPublisherAdapter, error) {
	// Verify connection is not nil and not closed
	if conn == nil {
		return nil, fmt.Errorf("rabbitmq connection is nil")
	}
	if conn.IsClosed() {
		return nil, fmt.Errorf("rabbitmq connection is already closed")
	}

	// Create channel with detailed error logging
	channel, err := conn.Channel()
	if err != nil {
		logger.Error("failed to create RabbitMQ channel", zap.Error(err))
		return nil, fmt.Errorf("failed to create channel: %w", err)
	}

	// Set channel QoS for better flow control
	err = channel.Qos(
		100,   // prefetch count
		0,     // prefetch size
		false, // global
	)
	if err != nil {
		logger.Warn("failed to set channel QoS, continuing anyway", zap.Error(err))
		// Don't fail on QoS error, it's not critical
	}

	// Declare exchange for MPC events with detailed logging
	logger.Info("declaring RabbitMQ exchange for MPC events")
	err = channel.ExchangeDeclare(
		"mpc.events", // name
		"topic",      // type
		true,         // durable
		false,        // auto-deleted
		false,        // internal
		false,        // no-wait
		nil,          // arguments
	)
	if err != nil {
		logger.Error("failed to declare mpc.events exchange", zap.Error(err))
		channel.Close()
		return nil, fmt.Errorf("failed to declare exchange: %w", err)
	}
	logger.Info("successfully declared mpc.events exchange")

	// Declare exchange for party messages with detailed logging
	logger.Info("declaring RabbitMQ exchange for party messages")
	err = channel.ExchangeDeclare(
		"mpc.messages", // name
		"direct",       // type
		true,           // durable
		false,          // auto-deleted
		false,          // internal
		false,          // no-wait
		nil,            // arguments
	)
	if err != nil {
		logger.Error("failed to declare mpc.messages exchange", zap.Error(err))
		channel.Close()
		return nil, fmt.Errorf("failed to declare messages exchange: %w", err)
	}
	logger.Info("successfully declared mpc.messages exchange")

	// Setup channel close notification
	closeChan := make(chan *amqp.Error, 1)
	channel.NotifyClose(closeChan)
	go func() {
		err := <-closeChan
		if err != nil {
			logger.Error("RabbitMQ channel closed unexpectedly", zap.Error(err))
		}
	}()

	logger.Info("EventPublisherAdapter initialized successfully")
	return &EventPublisherAdapter{
		conn:    conn,
		channel: channel,
	}, nil
}

// PublishEvent publishes an event to a topic
func (a *EventPublisherAdapter) PublishEvent(ctx context.Context, topic string, event interface{}) error {
	a.mu.Lock()
	defer a.mu.Unlock()

	body, err := json.Marshal(event)
	if err != nil {
		return fmt.Errorf("failed to marshal event: %w", err)
	}

	err = a.channel.PublishWithContext(
		ctx,
		"mpc.events", // exchange
		topic,        // routing key
		false,        // mandatory
		false,        // immediate
		amqp.Publishing{
			ContentType:  "application/json",
			DeliveryMode: amqp.Persistent,
			Body:         body,
		},
	)
	if err != nil {
		logger.Error("failed to publish event",
			zap.String("topic", topic),
			zap.Error(err))
		return fmt.Errorf("failed to publish event: %w", err)
	}

	logger.Debug("published event",
		zap.String("topic", topic),
		zap.Int("body_size", len(body)))

	return nil
}

// PublishMessage publishes a message to a specific party's queue
func (a *EventPublisherAdapter) PublishMessage(ctx context.Context, partyID string, message interface{}) error {
	a.mu.Lock()
	defer a.mu.Unlock()

	// Ensure queue exists for the party
	queueName := fmt.Sprintf("mpc.party.%s", partyID)
	_, err := a.channel.QueueDeclare(
		queueName, // name
		true,      // durable
		false,     // delete when unused
		false,     // exclusive
		false,     // no-wait
		nil,       // arguments
	)
	if err != nil {
		return fmt.Errorf("failed to declare queue: %w", err)
	}

	// Bind queue to exchange
	err = a.channel.QueueBind(
		queueName,      // queue name
		partyID,        // routing key
		"mpc.messages", // exchange
		false,          // no-wait
		nil,            // arguments
	)
	if err != nil {
		return fmt.Errorf("failed to bind queue: %w", err)
	}

	body, err := json.Marshal(message)
	if err != nil {
		return fmt.Errorf("failed to marshal message: %w", err)
	}

	err = a.channel.PublishWithContext(
		ctx,
		"mpc.messages", // exchange
		partyID,        // routing key
		false,          // mandatory
		false,          // immediate
		amqp.Publishing{
			ContentType:  "application/json",
			DeliveryMode: amqp.Persistent,
			Body:         body,
		},
	)
	if err != nil {
		logger.Error("failed to publish message",
			zap.String("party_id", partyID),
			zap.Error(err))
		return fmt.Errorf("failed to publish message: %w", err)
	}

	logger.Debug("published message to party",
		zap.String("party_id", partyID),
		zap.Int("body_size", len(body)))

	return nil
}

// Subscribe subscribes to a topic and returns a channel of messages
func (a *EventPublisherAdapter) Subscribe(ctx context.Context, topic string) (<-chan []byte, error) {
	a.mu.Lock()
	defer a.mu.Unlock()

	// Declare a temporary queue
	queue, err := a.channel.QueueDeclare(
		"",    // name (auto-generated)
		false, // durable
		true,  // delete when unused
		true,  // exclusive
		false, // no-wait
		nil,   // arguments
	)
	if err != nil {
		return nil, fmt.Errorf("failed to declare queue: %w", err)
	}

	// Bind queue to exchange with topic
	err = a.channel.QueueBind(
		queue.Name,   // queue name
		topic,        // routing key
		"mpc.events", // exchange
		false,        // no-wait
		nil,          // arguments
	)
	if err != nil {
		return nil, fmt.Errorf("failed to bind queue: %w", err)
	}

	// Start consuming
	msgs, err := a.channel.Consume(
		queue.Name, // queue
		"",         // consumer
		true,       // auto-ack
		false,      // exclusive
		false,      // no-local
		false,      // no-wait
		nil,        // args
	)
	if err != nil {
		return nil, fmt.Errorf("failed to register consumer: %w", err)
	}

	// Create output channel
	out := make(chan []byte, 100)

	// Start goroutine to forward messages
	go func() {
		defer close(out)
		for {
			select {
			case <-ctx.Done():
				return
			case msg, ok := <-msgs:
				if !ok {
					return
				}
				select {
				case out <- msg.Body:
				case <-ctx.Done():
					return
				}
			}
		}
	}()

	return out, nil
}

// SubscribePartyMessages subscribes to messages for a specific party
func (a *EventPublisherAdapter) SubscribePartyMessages(ctx context.Context, partyID string) (<-chan []byte, error) {
	a.mu.Lock()
	defer a.mu.Unlock()

	queueName := fmt.Sprintf("mpc.party.%s", partyID)

	// Ensure queue exists
	_, err := a.channel.QueueDeclare(
		queueName, // name
		true,      // durable
		false,     // delete when unused
		false,     // exclusive
		false,     // no-wait
		nil,       // arguments
	)
	if err != nil {
		return nil, fmt.Errorf("failed to declare queue: %w", err)
	}

	// Bind queue to exchange
	err = a.channel.QueueBind(
		queueName,      // queue name
		partyID,        // routing key
		"mpc.messages", // exchange
		false,          // no-wait
		nil,            // arguments
	)
	if err != nil {
		return nil, fmt.Errorf("failed to bind queue: %w", err)
	}

	// Start consuming
	msgs, err := a.channel.Consume(
		queueName, // queue
		"",        // consumer
		true,      // auto-ack
		false,     // exclusive
		false,     // no-local
		false,     // no-wait
		nil,       // args
	)
	if err != nil {
		return nil, fmt.Errorf("failed to register consumer: %w", err)
	}

	// Create output channel
	out := make(chan []byte, 100)

	// Start goroutine to forward messages
	go func() {
		defer close(out)
		for {
			select {
			case <-ctx.Done():
				return
			case msg, ok := <-msgs:
				if !ok {
					return
				}
				select {
				case out <- msg.Body:
				case <-ctx.Done():
					return
				}
			}
		}
	}()

	return out, nil
}

// Close closes the connection
func (a *EventPublisherAdapter) Close() error {
	a.mu.Lock()
	defer a.mu.Unlock()

	if a.channel != nil {
		if err := a.channel.Close(); err != nil {
			logger.Error("failed to close channel", zap.Error(err))
		}
	}
	return nil
}

// Ensure interface compliance
var _ output.MessageBrokerPort = (*EventPublisherAdapter)(nil)
