package event

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

	"github.com/go-logr/logr"

	larkevent "github.com/larksuite/oapi-sdk-go/v3/event"
	larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
)

type LarkEventDispatcher struct {
	mu                sync.RWMutex
	logger            logr.Logger
	eventType2Handler map[string][]larkevent.EventHandler
}

func NewLarkEventDispatcher(logger logr.Logger) *LarkEventDispatcher {
	return &LarkEventDispatcher{
		logger:            logger,
		eventType2Handler: make(map[string][]larkevent.EventHandler),
	}
}

func (d *LarkEventDispatcher) OnMessageReceive(handler func(ctx context.Context, event *larkim.P2MessageReceiveV1) error) *LarkEventDispatcher {
	typ := "im.message.receive_v1"

	d.mu.Lock()
	defer d.mu.Unlock()

	d.eventType2Handler[typ] = append(d.eventType2Handler[typ], larkim.NewP2MessageReceiveV1Handler(handler))
	return d
}

func (d *LarkEventDispatcher) OnChatBotAdded(handler func(context.Context, *larkim.P2ChatMemberBotAddedV1) error) *LarkEventDispatcher {
	typ := "im.chat.member.bot_added_v1"

	d.mu.Lock()
	defer d.mu.Unlock()

	d.eventType2Handler[typ] = append(d.eventType2Handler[typ], larkim.NewP2ChatMemberBotAddedV1Handler(handler))
	return d
}

func (d *LarkEventDispatcher) Dispatch(ctx context.Context, body *larkevent.EventV2Body) error {
	d.mu.RLock()
	handlers, ok := d.eventType2Handler[body.Header.EventType]
	if !ok {
		d.mu.RUnlock()
		return nil
	}
	handlers = append([]larkevent.EventHandler(nil), handlers...)
	d.mu.RUnlock()

	if len(handlers) == 0 {
		return nil
	}

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

	event := handlers[0].Event()
	if err := json.Unmarshal(bodyJSON, event); err != nil {
		return fmt.Errorf("unmarshal event failed: %s", err)
	}

	for _, handler := range handlers {
		handler := handler
		go func() {
			if handleErr := handler.Handle(context.WithoutCancel(ctx), event); handleErr != nil {
				d.logger.Error(handleErr, "handle lark event failed", "event", event)
			}
		}()
	}

	return nil
}
