package event

import (
	"context"
	"fmt"
	"sync"

	"github.com/go-logr/logr"
	larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
)

type LarkCardHandler func(ctx context.Context, value string, cardAction *larkcard.CardAction) error

type LarkCardDispatcher struct {
	mu             sync.RWMutex
	logger         logr.Logger
	dispatchKey    string
	valueToHandler map[string]LarkCardHandler
}

func NewLarkCardDispatcher(dispatchKey string, logger logr.Logger) *LarkCardDispatcher {
	return &LarkCardDispatcher{
		logger:         logger,
		dispatchKey:    dispatchKey,
		valueToHandler: map[string]LarkCardHandler{},
	}
}

func (d *LarkCardDispatcher) Register(dispatchValue string, handler LarkCardHandler) {
	d.mu.Lock()
	defer d.mu.Unlock()

	d.valueToHandler[dispatchValue] = handler
}

func (d *LarkCardDispatcher) Dispatch(ctx context.Context, cardAction *larkcard.CardAction) (func(), error) {
	if cardAction.Action == nil {
		return nil, fmt.Errorf("received a lark card event but action is nil")
	}
	dispatchValueAny, exist := cardAction.Action.Value[d.dispatchKey]
	if !exist {
		return nil, fmt.Errorf("received a lark card event but dispatch key not found")
	}
	dispatchValue, ok := dispatchValueAny.(string)
	if !ok {
		return nil, fmt.Errorf("received a lark card event but dispatch value is not string")
	}

	d.mu.RLock()
	handler := d.valueToHandler[dispatchValue]
	d.mu.RUnlock()
	if handler == nil {
		return func() {}, nil
	}

	// card will be updated back after return success to lark,
	// so we need to handle the action after return success.
	startCh := make(chan struct{})

	go func() {
		<-startCh

		err := handler(context.WithoutCancel(ctx), dispatchValue, cardAction)
		if err != nil {
			d.logger.Error(err, "handle a card event failed", "card_action", cardAction)
			return
		}
	}()

	return func() {
		close(startCh)
	}, nil
}
