package eventbus

import (
	"fmt"
	"reflect"
	"time"

	"github.com/go-logr/logr"
	metrics "github.com/tidbcloud/devops/pkg/infra/metrics/devops-server"
	"github.com/tidbcloud/tiinf/transport/tieventbus"
)

type pubsub[E any] struct {
	topic  string
	logger logr.Logger
	bus    tieventbus.Eventbus

	subs map[string]func(uuid string, ev *E) error
}

func newPubSub[E any](topic string, logger logr.Logger, bus tieventbus.Eventbus) *pubsub[E] {
	return &pubsub[E]{
		topic:  topic,
		logger: logger.WithValues("topic", topic),
		bus:    bus,
		subs:   make(map[string]func(uuid string, ev *E) error),
	}
}

// Subscribe register a subscriber to eventbus.
//
// NOTE: MUST be called before eventbus.Run().
func (r *pubsub[E]) Subscribe(groupKey string, handle func(uuid string, ev *E) error) {
	r.subs[groupKey] = handle
}

// Publish publish a event to eventbus.
func (r *pubsub[E]) Publish(ev *E) error {
	err := r.bus.Publish(&tieventbus.Event{
		ID:   genUUID(r.topic),
		Type: r.topic,
		Data: ev,
	})
	metrics.EventBus.EventPushTotal.
		WithLabelValues(r.topic, metrics.ResultFromErr(err)).
		Inc()
	return err
}

// run register all subscribers to eventbus.
func (r *pubsub[E]) run() error {
	if len(r.subs) == 0 {
		r.logger.Info("define a pubsub with topic but no subscriber", "topic", r.topic)
	}

	for group, handle := range r.subs {
		var data E
		r.logger.Info("register a subscriber", "group_key", group, "handle_ptr", reflect.ValueOf(handle).Pointer())

		r.bus.RegisterHandler(
			tieventbus.HandlerConfig{
				EventType:  r.topic,
				Group:      group,
				HandleFunc: r.wrapHandler(group, handle),
				DataStruct: data,
			},
		)

	}

	return nil
}

func (r *pubsub[E]) wrapHandler(group string, h func(uuid string, ev *E) error) func(event *tieventbus.Event) error {
	return func(event *tieventbus.Event) error {
		logger := r.logger.WithValues("event_id", event.ID, "group_key", group)

		logger.V(4).Info("receive a event")

		if event.Type != r.topic {
			logger.Error(fmt.Errorf("mismatch event type"), "receive a event but type is incorrect", "event_type", event.Type)
			return nil
		}

		rEvent, ok := event.Data.(*E)
		if !ok {
			logger.Error(fmt.Errorf("mismatch event data type"), "receive a event but data is incorrect", "event_data_type", reflect.TypeOf(event.Data).Name())
			return nil
		}

		beginAt := time.Now()

		err := h(event.ID, rEvent)
		if err != nil {
			logger.Error(err, "handle a event failed")
		}

		metrics.EventBus.EventHandleDur.
			WithLabelValues(r.topic, metrics.ResultFromErr(err), group).
			Observe(time.Since(beginAt).Seconds())

		return err
	}
}
