package event

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

	"github.com/go-logr/logr"

	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/utils/container/gmap"
	strutil "github.com/tidbcloud/aisupport/packages/sdk-go/infra/utils/container/str"
)

type LoginData struct {
	ID        string          `json:"id"`
	Source    string          `json:"source"`
	Purpose   string          `json:"purpose"`
	UserEmail string          `json:"user_email"`
	Extra     json.RawMessage `json:"extra"`
}

type LarkLoginHandler func(ctx context.Context, data LoginData, userAccessToken string) error

type LarkLoginDispatcher interface {
	SaveLoginData(d LoginData) (string, error)
	GetLoginData(id string) (LoginData, bool)
	Register(source string, handler LarkLoginHandler)
	Dispatch(ctx context.Context, id string, userAccessToken string) error
}

func NewLarkLoginDispatcher(logger logr.Logger, stores ...LoginDataStore) LarkLoginDispatcher {
	store := LoginDataStore(NewMemLoginDataStore())
	if len(stores) > 0 {
		store = stores[0]
	}

	return &loginDispatcher{
		logger:         logger,
		store:          store,
		source2Handler: map[string]LarkLoginHandler{},
	}
}

type LoginDataStore interface {
	Save(d LoginData) (string, error)
	Load(id string) (LoginData, bool)
	Consume(id string) (LoginData, bool)
}

type memLoginDataStore struct {
	data *gmap.Map[string, LoginData]
}

func NewMemLoginDataStore() *memLoginDataStore {
	return &memLoginDataStore{
		data: gmap.NewMap[string, LoginData](),
	}
}

func (s *memLoginDataStore) Save(ld LoginData) (string, error) {
	ld.ID = strutil.RandomString(32)
	s.data.Store(ld.ID, ld)
	return ld.ID, nil
}

func (s *memLoginDataStore) Load(id string) (LoginData, bool) {
	return s.data.Load(id)
}

func (s *memLoginDataStore) Consume(id string) (LoginData, bool) {
	return s.data.LoadAndDelete(id)
}

type loginDispatcher struct {
	mu             sync.RWMutex
	logger         logr.Logger
	store          LoginDataStore
	source2Handler map[string]LarkLoginHandler
}

func (d *loginDispatcher) SaveLoginData(data LoginData) (string, error) {
	return d.store.Save(data)
}

func (d *loginDispatcher) GetLoginData(id string) (LoginData, bool) {
	return d.store.Load(id)
}

func (d *loginDispatcher) Register(source string, handler LarkLoginHandler) {
	d.mu.Lock()
	defer d.mu.Unlock()

	d.source2Handler[source] = handler
}

func (d *loginDispatcher) Dispatch(ctx context.Context, id string, userAccessToken string) error {
	data, ok := d.store.Consume(id)
	if !ok {
		return nil
	}

	d.mu.RLock()
	handler := d.source2Handler[data.Source]
	d.mu.RUnlock()
	if handler == nil {
		return nil
	}

	go func() {
		if err := handler(context.WithoutCancel(ctx), data, userAccessToken); err != nil {
			d.logger.Error(err, "handle lark login data failed", "login_data", data)
		}
	}()

	return nil
}
