package events2hub

import (
	"context"
	"encoding/base64"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pingcap/log"
	accountpb "github.com/tidbcloud/account/idl/pbgen/proto/account"
	coresvc "github.com/tidbcloud/core-svc/event"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/eventbus-datastruct/event"
	"github.com/tidbcloud/tiinf/transport/tieventbus"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic_cloud_server/pkg/client/account"
	"github.com/pingcap-inc/clinic_cloud_server/pkg/client/activityhub"
	"github.com/pingcap-inc/clinic_cloud_server/pkg/client/core_svc"
)

const (
	defaultEventDuration = 15 * time.Second
)

type EventPayload struct {
	Author       string                `json:"author"`
	Summary      string                `json:"summary"`
	ClusterEvent *coresvc.ClusterEvent `json:"cluster_event"`
}

func StartEventHandler(
	hubC *activityhub.ActivityHubClient,
	accountC *account.Client,
	coreC *core_svc.Client,
	ebCfg tieventbus.EventbusConfig,
) {
	eventbus, err := tieventbus.NewEventbus(ebCfg)
	if err != nil {
		log.Error("tieventbus.NewEventbus failed", zap.Error(err))
		return
	}

	displayEventHandler := tieventbus.HandlerConfig{
		EventType: event.DisplayEventTopic,
		Group:     "events2hub",
		HandleFunc: func(ev *tieventbus.Event) error {
			log.Info("received event", zap.String("UUID", ev.ID), zap.String("type", ev.Type), zap.Any("payload", ev.Data))

			eventBody, ok := ev.Data.(*event.DisplayEvent)
			if !ok {
				log.Error("event is not a DisplayEvent", logFields(ev, eventBody)...)
				return nil
			}

			// 1677576600, event center release timestamp
			if eventBody.HappenedAtMs < 1677576600*1000 || eventBody.HappenedAtMs > 1677576600*1000*1000 {
				log.Warn("event happened at invalid time", logFields(ev, eventBody)...)
				return nil
			}

			var cluster *core.Cluster
			if eventBody.Author == "" {
				ctx, cancel := context.WithTimeout(context.Background(), defaultEventDuration)
				defer cancel()
				resp, err := accountC.GetUserByID(ctx, &accountpb.GetUserByIDRequest{UserId: eventBody.AuthorID})
				if err != nil && eventBody.AuthorID != 0 {
					log.Warn("failed to get user info", append(logFields(ev, eventBody), zap.Error(err))...)
				} else {
					user := resp.GetUser()
					switch {
					case user.GetId() <= 0:
						eventBody.Author = "system"
					case strings.HasSuffix(user.GetEmail(), "system@pingcap.com"):
						eventBody.Author = "openapi"
					case strings.HasSuffix(user.GetEmail(), "@pingcap.com"):
						ctx, cancel := context.WithTimeout(context.Background(), defaultEventDuration)
						defer cancel()

						cluster, err = coreC.GetCluster(ctx, eventBody.ClusterID)
						if err != nil {
							log.Error("failed to get cluster info", append(logFields(ev, eventBody), zap.Error(err))...)
						}
						creatorEmail := getClusterCreator(accountC, cluster)
						if strings.HasSuffix(creatorEmail, "@pingcap.com") {
							eventBody.Author = user.GetEmail()
						}
						// default to Technical Support
						if eventBody.Author == "" {
							eventBody.Author = "Technical Support"
						}
					default:
						eventBody.Author = user.GetEmail()
					}
				}
			}

			if eventBody.ProjectID == 0 || eventBody.TenantID == 0 {
				if cluster == nil {
					ctx, cancel := context.WithTimeout(context.Background(), defaultEventDuration)
					defer cancel()
					cluster, err = coreC.GetCluster(ctx, eventBody.ClusterID)
					if err != nil {
						log.Error("failed to get cluster info", append(logFields(ev, eventBody), zap.Error(err))...)
					} else {
						eventBody.ProjectID = cluster.GetProjectId()
						eventBody.TenantID = cluster.GetTenantId()
					}
				}
			}

			return hubC.SendActivity(activityhub.Activity{
				Type:          "Event",
				Name:          eventBody.DisplayEventType,
				ActivityID:    "eventbus-" + ev.ID,
				ApplicationID: strconv.FormatUint(eventBody.TenantID, 10),
				ConceptID:     strconv.FormatUint(eventBody.ProjectID, 10),
				TargetID:      strconv.FormatUint(eventBody.ClusterID, 10),
				// rawbase64 - clusterID - contextID
				ContextID:       fmt.Sprintf("%s-%d-%s", base64.RawStdEncoding.EncodeToString([]byte(eventBody.DisplayEventType)), eventBody.ClusterID, eventBody.ContextID),
				CalibrationTime: int64(eventBody.HappenedAtMs / 1000),
				Author:          strings.ToLower(eventBody.Author),
				Tags: map[string]string{
					"status": string(eventBody.DisplayStatus),
				},
				Payload: eventBody,
			})
		},
		DataStruct: event.DisplayEvent{},
	}
	eventbus.RegisterHandler(displayEventHandler)

	err = eventbus.Run()
	if err != nil {
		panic(err)
	}
}

func getClusterCreator(accountC *account.Client, cluster *core.Cluster) string {
	ctx, cancel := context.WithTimeout(context.Background(), defaultEventDuration)
	defer cancel()
	resp, err := accountC.GetUserByID(ctx, &accountpb.GetUserByIDRequest{UserId: cluster.GetCreatedBy()})
	if err != nil {
		log.Error("failed to get user info", zap.Uint64("userID", cluster.GetCreatedBy()), zap.Error(err))
	} else {
		return resp.GetUser().GetEmail()
	}
	// return false when error to avoid email leak
	return ""
}

func logFields(ev *tieventbus.Event, e *event.DisplayEvent) []zap.Field {
	return []zap.Field{
		zap.String("event id", ev.ID),
		zap.String("event type", e.DisplayEventType),
		zap.String("event status", string(e.DisplayStatus)),
		zap.Uint64("cluster id ", e.ClusterID),
		zap.Uint64("tenant id ", e.TenantID),
		zap.String("author", e.Author),
		zap.Uint64("happened at", e.HappenedAtMs),
	}
}
