package rabbitmq

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"

	"github.com/wagslane/go-rabbitmq"

	"code.icb4dc0.de/prskr/pg_v_man/core/domain"
	"code.icb4dc0.de/prskr/pg_v_man/core/ports"
	"code.icb4dc0.de/prskr/pg_v_man/infrastructure/config"
)

var _ ports.ReplicationEventConsumer = (*PublishingEventConsumer)(nil)

func NewPublishingEventConsumer(ctx context.Context, cfg config.RabbitMQ) (consumer *PublishingEventConsumer, err error) {
	var dialer net.Dialer
	conn, err := rabbitmq.NewConn(cfg.ConnectionString, rabbitmq.WithConnectionOptionsLogging, func(options *rabbitmq.ConnectionOptions) {
		options.Config.Dial = func(network, addr string) (net.Conn, error) {
			return dialer.DialContext(ctx, network, addr)
		}
	})
	if err != nil {
		return nil, fmt.Errorf("could not create RabbitMQ connection: %w", err)
	}

	defer func() {
		if err != nil {
			err = errors.Join(err, conn.Close())
		}
	}()

	publisherOptions := []func(*rabbitmq.PublisherOptions){
		rabbitmq.WithPublisherOptionsLogging,
		rabbitmq.WithPublisherOptionsExchangeName(cfg.Exchange.Name),
		rabbitmq.WithPublisherOptionsExchangeKind(cfg.Exchange.Kind),
		rabbitmq.WithPublisherOptionsExchangeDeclare,
	}

	if cfg.Exchange.Durable {
		publisherOptions = append(publisherOptions, rabbitmq.WithPublisherOptionsExchangeDurable)
	}

	publisher, err := rabbitmq.NewPublisher(
		conn,
		publisherOptions...,
	)
	if err != nil {
		return nil, fmt.Errorf("could not create RabbitMQ publisher: %w", err)
	}

	return &PublishingEventConsumer{Conn: conn, Publisher: publisher, Cfg: cfg}, nil
}

type PublishingEventConsumer struct {
	Conn      *rabbitmq.Conn
	Publisher *rabbitmq.Publisher
	Cfg       config.RabbitMQ
}

func (p PublishingEventConsumer) OnDataChange(ctx context.Context, ev domain.ReplicationEvent) error {
	data, err := json.Marshal(ev)
	if err != nil {
		return fmt.Errorf("could not marshal event: %w", err)
	}
	return p.Publisher.PublishWithContext(
		ctx, data, []string{p.Cfg.RoutingKey, strings.Join([]string{ev.DBName, ev.Namespace, ev.Relation}, ".")},
		rabbitmq.WithPublishOptionsContentType("application/json"),
		rabbitmq.WithPublishOptionsExchange(p.Cfg.Exchange.Name),
		rabbitmq.WithPublishOptionsCorrelationID(strconv.Itoa(int(ev.TransactionId))),
		rabbitmq.WithPublishOptionsType(ev.EventType.String()),
		rabbitmq.WithPublishOptionsHeaders(rabbitmq.Table{
			"db":        ev.DBName,
			"namespace": ev.Namespace,
			"relation":  ev.Relation,
		}),
	)
}

func (p PublishingEventConsumer) Close() error {
	p.Publisher.Close()

	return p.Conn.Close()
}