// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package rabbitmq_test

import (
	"fmt"
	"log"
	"log/slog"
	"os"
	"os/signal"
	"syscall"
	"testing"

	smqlog "github.com/absmach/supermq/logger"
	"github.com/absmach/supermq/pkg/messaging"
	"github.com/absmach/supermq/pkg/messaging/rabbitmq"
	"github.com/ory/dockertest/v3"
	amqp "github.com/rabbitmq/amqp091-go"
	"github.com/stretchr/testify/assert"
	"google.golang.org/protobuf/proto"
)

const (
	port          = "5672/tcp"
	brokerName    = "rabbitmq"
	brokerVersion = "3.12.12-alpine"
)

var (
	publisher messaging.Publisher
	pubsub    messaging.PubSub
	logger    *slog.Logger
	address   string
)

func TestMain(m *testing.M) {
	pool, err := dockertest.NewPool("")
	if err != nil {
		log.Fatalf("Could not connect to docker: %s", err)
	}

	container, err := pool.Run(brokerName, brokerVersion, []string{})
	if err != nil {
		log.Fatalf("Could not start container: %s", err)
	}
	handleInterrupt(pool, container)

	address = fmt.Sprintf("amqp://%s:%s", "localhost", container.GetPort(port))
	if err := pool.Retry(func() error {
		publisher, err = rabbitmq.NewPublisher(address)
		return err
	}); err != nil {
		log.Fatalf("Could not connect to docker: %s", err)
	}

	logger, err = smqlog.New(os.Stdout, "debug")
	if err != nil {
		log.Fatal(err.Error())
	}
	if err := pool.Retry(func() error {
		pubsub, err = rabbitmq.NewPubSub(address, logger)
		return err
	}); err != nil {
		log.Fatalf("Could not connect to docker: %s", err)
	}

	code := m.Run()

	if err := pool.Purge(container); err != nil {
		log.Fatalf("Could not purge container: %s", err)
	}

	os.Exit(code)
}

func newConn() (*amqp.Connection, *amqp.Channel, error) {
	conn, err := amqp.Dial(address)
	if err != nil {
		return nil, nil, err
	}
	ch, err := conn.Channel()
	if err != nil {
		return nil, nil, err
	}
	if err := ch.ExchangeDeclare(exchangeName, amqp.ExchangeTopic, true, false, false, false, nil); err != nil {
		return nil, nil, err
	}

	return conn, ch, nil
}

func rabbitHandler(deliveries <-chan amqp.Delivery, h messaging.MessageHandler) {
	for d := range deliveries {
		var msg messaging.Message
		if err := proto.Unmarshal(d.Body, &msg); err != nil {
			logger.Warn(fmt.Sprintf("Failed to unmarshal received message: %s", err))
			return
		}
		if err := h.Handle(&msg); err != nil {
			logger.Warn(fmt.Sprintf("Failed to handle SuperMQ message: %s", err))
			return
		}
	}
}

func subscribe(t *testing.T, ch *amqp.Channel, topic string) <-chan amqp.Delivery {
	_, err := ch.QueueDeclare(topic, true, true, true, false, nil)
	assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))

	err = ch.QueueBind(topic, topic, exchangeName, false, nil)
	assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))

	clientID := fmt.Sprintf("%s-%s", topic, clientID)
	msgs, err := ch.Consume(topic, clientID, true, false, false, false, nil)
	assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))

	return msgs
}

func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) {
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		if err := pool.Purge(container); err != nil {
			log.Fatalf("Could not purge container: %s", err)
		}
		os.Exit(0)
	}()
}
