package publish

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/account/internel/dao/entity"

	"github.com/google/uuid"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/commons/eventbus"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const (
	EventVersion = "1.0"

	BizEventUserRoleBindingChanged   = "user.role_binding.changed"
	BizEventUserRemovedFromOrg       = "user.removed_from_org"
	BizEventUserChanged              = "user.changed"
	BizEventOrgChanged               = "org.changed"
	BizEventApiKeyRoleBindingChanged = "apikey.role_binding.changed"
	BizEventApiKeyDeleted            = "apikey.deleted"
)

type SyncEvent struct {
	EventID   string       `json:"event_id"`
	EventType string       `json:"event_type"`
	Version   string       `json:"version"`
	Timestamp time.Time    `json:"timestamp"`
	Data      BizEventData `json:"data"`
}

type BizEventData struct {
	User         *UserData          `json:"user"`
	ApiKey       *ApiKeyData        `json:"api_key"`
	Org          *OrgData           `json:"org"`
	RoleBindings []RoleBindingEntry `json:"role_bindings"`
}

type UserData struct {
	UserID    uint64 `json:"user_id"`
	Email     string `json:"email"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

type ApiKeyData struct {
	ApiKeyID    uint64 `json:"api_key_id"`
	AccessKey   string `json:"access_key"`
	Description string `json:"description"`
}

type OrgData struct {
	OrgID    uint64 `json:"org_id"`
	OrgName  string `json:"org_name"`
	Kind     string `json:"kind"`
	TimeZone string `json:"time_zone"`
	Country  string `json:"country"`
	Status   string `json:"status"`
}

type RoleBindingEntry struct {
	ScopeType string `json:"scope_type"`
	ScopeID   uint64 `json:"scope_id"`
	Role      string `json:"role"`
}

func PublishDataChangeEvent(ctx context.Context, bizEventType string, data BizEventData) error {
	if config.IsLocalEnv() {
		tilog.Info(ctx, fmt.Sprintf("PublishDataChangeEvent skip in %s", config.GetEnv()))
		return nil
	}

	syncEvent := SyncEvent{
		EventID:   uuid.New().String(),
		EventType: bizEventType,
		Version:   EventVersion,
		Timestamp: time.Now().UTC(),
		Data:      data,
	}

	syncEventType := config.EventTypeAccountDataChangeEvent

	event := &eventbus.Event{
		EventID:   config.Group,
		EventType: syncEventType,
		EventData: syncEvent,
	}

	if err := eventbus.Get().PublishEvent(syncEventType, event); err != nil {
		tilog.Error(ctx, "PublishDataChangeEvent error", zap.Any("event", syncEvent), zap.Any("err", err))
		return err
	}
	tilog.Info(ctx, "PublishDataChangeEvent success", zap.Any("event", syncEvent))
	return nil
}

func PublishDataChangeEventWithEntData(ctx context.Context, bizEventType string, user *entity.User, key *entity.ApiKey, org *entity.Tenant, rbs []entity.RoleBinding) error {
	if user == nil && key == nil && org == nil && len(rbs) == 0 {
		tilog.Info(ctx, "PublishDataChangeEventWithEntData not data change, ignore", zap.Any("bizEventType", bizEventType))
	}

	var bizEventData BizEventData
	if user != nil {
		userData := &UserData{
			UserID:    user.ID.Uint64(),
			Email:     user.Email,
			FirstName: user.FirstName,
			LastName:  user.LastName,
		}
		bizEventData.User = userData
	}
	if key != nil {
		keyData := &ApiKeyData{
			ApiKeyID:    key.ID.Uint64(),
			AccessKey:   key.AccessKey,
			Description: key.Description,
		}
		bizEventData.ApiKey = keyData
	}
	if org != nil {
		orgData := &OrgData{
			OrgID:    org.ID.Uint64(),
			OrgName:  org.Name,
			Kind:     string(org.Kind),
			TimeZone: org.TimeZone,
			Country:  org.Country,
			Status:   string(org.Status),
		}
		bizEventData.Org = orgData
	}

	if len(rbs) > 0 {
		var rbEntrys []RoleBindingEntry
		for _, rb := range rbs {
			entry := RoleBindingEntry{
				ScopeType: rb.ScopeType.String(),
				ScopeID:   rb.ScopeID.Uint64(),
				Role:      rb.Role,
			}
			rbEntrys = append(rbEntrys, entry)
		}
		bizEventData.RoleBindings = rbEntrys
	}

	return PublishDataChangeEvent(ctx, bizEventType, bizEventData)
}
