package clustereventconsumer

import (
	"context"
	"fmt"
	"time"

	"github.com/samber/lo"
	accountsdk "github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/transport/tieventbus"
	"go.uber.org/zap"
	"gorm.io/gorm/clause"

	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	eventutil "github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
)

const (
	starterInventoryDefaultLimit            = int64(100)
	starterInventoryThresholdPercent        = int64(80)
	starterInventoryThresholdCurrentMinimum = starterInventoryDefaultLimit * starterInventoryThresholdPercent / 100
)

var starterInventoryUnlimitedFeatureFlags = []string{
	"instance_capacity_plan_billed_overage",
	"instance_capacity_plan_free_overage",
}

func (c *clusterEventConsumerManagerImp) checkStarterInventoryThresholdAndPublish(
	ctx context.Context,
	ev *tieventbus.Event,
) error {
	eventBody, ok := ev.Data.(*eventutil.ClusterEvent)
	if !ok {
		return nil
	}

	if eventBody.EventType != eventutil.EventTypeCreated {
		return nil
	}
	if eventBody.ServicePlan != openapicommonv1beta1.ServicePlan_Starter.String() {
		return nil
	}

	hitFlagNames, err := c.accountCli.ListHitFeatureFlagNames(ctx, &accountsdk.ListHitFeatureFlagNamesReq{OrgId: eventBody.TenantID})
	if err != nil {
		tilog.Error(ctx, "starter inventory threshold: list feature flags failed", zap.Error(err), zap.Uint64("tenant_id", eventBody.TenantID))
		return err
	}
	if lo.ContainsBy(hitFlagNames, func(flagName string) bool {
		return lo.Contains(starterInventoryUnlimitedFeatureFlags, flagName)
	}) {
		return nil
	}

	currentCount, err := c.clusterRepo.CountClusterByFilter(
		ctx,
		&repository.ClusterFilter{
			OrganizationIds: []uint64{eventBody.TenantID},
			ServicePlans:    []string{openapicommonv1beta1.ServicePlan_Starter.String()},
			QueryDeleted:    false,
			DeployType:      entity.DeployTypeServerless,
		},
	)
	if err != nil {
		tilog.Error(ctx, "starter inventory threshold: count starter clusters failed", zap.Error(err), zap.Uint64("tenant_id", eventBody.TenantID))
		return err
	}

	if currentCount < starterInventoryThresholdCurrentMinimum {
		return nil
	}

	limit := starterInventoryDefaultLimit
	threshold := starterInventoryThresholdPercent
	current := currentCount
	publishedEvent := &eventutil.ClusterEvent{
		TenantID:         eventBody.TenantID,
		ProjectID:        eventBody.ProjectID,
		ClusterID:        eventBody.ClusterID,
		TraceID:          eventBody.TraceID,
		OperatorID:       eventBody.OperatorID,
		UserEmail:        eventBody.UserEmail,
		OpenAPIPublicKey: eventBody.OpenAPIPublicKey,
		OperationID:      eventBody.OperationID,
		IsDevTier:        eventBody.IsDevTier,
		Status:           eventBody.Status,
		EventType:        eventutil.EventTypeStarterInventoryThresholdReached,
		EventCreatedAtMs: eventBody.EventCreatedAtMs,
		ServicePlan:      eventBody.ServicePlan,
		ClusterType:      eventutil.ClusterTypeTidbxInstance,
		ExtraInfo: &eventutil.EventExtraInfo{
			InventoryLimit:            &limit,
			InventoryCurrentCount:     &current,
			InventoryThresholdPercent: &threshold,
		},
	}

	if err := c.publishStarterInventoryThresholdEvent(ctx, publishedEvent); err != nil {
		tilog.Error(ctx, "starter inventory threshold: write threshold event failed", zap.Error(err), zap.Uint64("tenant_id", eventBody.TenantID))
		return err
	}

	return nil
}

func (c *clusterEventConsumerManagerImp) publishStarterInventoryThresholdEvent(
	ctx context.Context,
	ev *eventutil.ClusterEvent,
) error {
	day := time.UnixMilli(ev.EventCreatedAtMs).UTC().Format("2006-01-02")
	uuid := fmt.Sprintf("starter-inventory-threshold-%d-%s-%s", ev.TenantID, ev.ServicePlan, day)
	payload, err := ev.MarshalJsonPayload()
	if err != nil {
		return err
	}

	eventModel := &eventutil.EventModel{
		Topic:     eventutil.TopicClusterEvent.String(),
		UUID:      uuid,
		Payload:   payload,
		Timestamp: time.UnixMilli(ev.EventCreatedAtMs),
		Status:    eventutil.StatusCreated,
	}

	result := c.dbProvider.WriteDB(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(eventModel)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		tilog.Info(ctx,
			"starter inventory threshold: duplicate event in same day, skip",
			zap.String("event_uuid", uuid),
			zap.Uint64("tenant_id", ev.TenantID),
			zap.String("service_plan", ev.ServicePlan),
		)
		return nil
	}

	return nil
}
