package service

import (
	"context"
	"encoding/json"
	"strconv"
	"time"

	"github.com/lithammer/shortuuid/v3"
	core_event "github.com/tidbcloud/eventbus-datastruct/event"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/infra/eventbus"
	"github.com/tidbcloud/serverless-service/internal/utils"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tiinf/transport/tieventbus"
	"go.uber.org/zap"
)

func authorIDFromCtx(ctx context.Context) uint64 {
	user, err := utils.GetUserFromContext(ctx)
	if err != nil {
		return 0
	}
	return user.ID
}

// authorFromCtx records the current actor, not the resource creator.
// Author is left empty when AuthorID is set so Events Center can fill the email.
func authorFromCtx(ctx context.Context) (string, uint64) {
	if authorID := authorIDFromCtx(ctx); authorID != 0 {
		return "", authorID
	}
	author := utils.BuildCreatorID(ctx)
	if author == utils.UnknownUserIDKey {
		return "", 0
	}
	return author, 0
}

func marshalEventDetail(v any) string {
	if v == nil {
		return ""
	}
	b, err := json.Marshal(v)
	if err != nil {
		return ""
	}
	return string(b)
}

func parseUintID(id string) uint64 {
	n, _ := strconv.ParseUint(id, 10, 64)
	return n
}

func publishDisplayEvent(
	ctx context.Context,
	bus *eventbus.EventbusService,
	cluster *tidbmgmtservicev1.Cluster,
	eventType string,
	summary string,
	detail any,
) {
	if bus == nil {
		return
	}
	if cluster == nil {
		log.WithContextL(ctx).Error("skip display event: cluster missing", zap.String("eventType", eventType))
		return
	}
	author, authorID := authorFromCtx(ctx)
	data := &core_event.DisplayEvent{
		DisplayEventType: eventType,
		DisplayStatus:    core_event.EventStatusSucceeded,
		Severity:         core_event.EventSeverityInfo,
		Summary:          summary,
		HappenedAtMs:     uint64(time.Now().UTC().UnixMilli()),
		Author:           author,
		AuthorID:         authorID,
		TenantID:         cluster.OrganizationId,
		ProjectID:        cluster.ProjectId,
		ClusterID:        cluster.ClusterId,
		Detail:           marshalEventDetail(detail),
	}
	if err := bus.Publish(&tieventbus.Event{
		ID:   shortuuid.New(),
		Type: core_event.DisplayEventTopic,
		Data: data,
	}); err != nil {
		log.WithContextL(ctx).Error("failed to publish events center", zap.Error(err))
	}
}

func publishDisplayEventAsync(
	ctx context.Context,
	bus *eventbus.EventbusService,
	loadCluster func(context.Context) *tidbmgmtservicev1.Cluster,
	eventType string,
	summary string,
	detail any,
) {
	if bus == nil {
		return
	}
	rctx := context.WithoutCancel(ctx)
	go func() {
		publishDisplayEvent(rctx, bus, loadCluster(rctx), eventType, summary, detail)
	}()
}

func loadClusterForEvent(
	ctx context.Context,
	mgmt facade.MgmtSvcFacade,
	clusterID string,
) *tidbmgmtservicev1.Cluster {
	id := parseUintID(clusterID)
	if id == 0 {
		log.WithContextL(ctx).Error("invalid cluster id for display event", zap.String("clusterID", clusterID))
		return nil
	}
	cluster, err := mgmt.GetClusterIncludeDeleted(ctx, id)
	if err != nil {
		log.WithContextL(ctx).
			Error("failed to get cluster for display event", zap.Error(err), zap.Uint64("clusterID", id))
		return nil
	}
	return cluster
}
