package common

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	"github.com/shopspring/decimal"
	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/deploy"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/common/utils"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/serverless/task"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type VMRule struct {
	*task.BaseTask
}

func NewVMRule(ctx *task.Context) *VMRule {
	return &VMRule{BaseTask: &task.BaseTask{Context: ctx}}
}

func (t *VMRule) Name() string {
	return "VMRule"
}

func (t *VMRule) Create(ctx context.Context) deploy.Result {
	desiredRule := &vmv1beta1.VMRule{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.Cluster.Name,
			Namespace: t.Cluster.Namespace,
		},
	}

	if t.Cluster.Spec.MuteAlert {
		return deploy.ErrorOrDone(utils.EnsureObjectDeleted(ctx, t.Remote, desiredRule))
	}

	templateID := t.Cluster.Labels[constants.LabelTemplateID]
	if templateID == "" {
		return deploy.Done()
	}

	templateRule := &vmv1beta1.VMRule{}
	err := t.Remote.Get(ctx, types.NamespacedName{
		Name:      templateID,
		Namespace: constants.AlertRuleTemplateNamespace,
	}, templateRule)
	if err != nil {
		if apierrors.IsNotFound(err) {
			t.Logger.Info("rule template not found", "templateID", templateID)
			return deploy.Done()
		}
		return deploy.Error(fmt.Errorf("get template rule %s failed: %w", templateID, err))
	}

	overlayRule := &vmv1beta1.VMRule{}
	overlayRuleID := fmt.Sprintf(constants.OVERLAY_NAME_TEMPATE, t.Cluster.Name)
	err = t.Remote.Get(ctx, types.NamespacedName{
		Name:      overlayRuleID,
		Namespace: t.Cluster.Namespace,
	}, overlayRule)
	if client.IgnoreNotFound(err) != nil {
		return deploy.Error(fmt.Errorf("get overlay rule %s failed: %w", overlayRuleID, err))
	}

	mergedSpec := alertRuleMerge(templateRule.Spec, overlayRule.Spec, t.Cluster)

	if _, err := controllerutil.CreateOrUpdate(ctx, t.Remote, desiredRule, func() error {
		if desiredRule.Labels == nil {
			desiredRule.Labels = make(map[string]string)
		}
		desiredRule.Labels[constants.LabelMultiTenancyRule] = "true"
		desiredRule.Labels[constants.LabelTenantID] = t.Cluster.Labels[constants.LabelTenantID]
		desiredRule.Labels[constants.LabelBizType] = t.Cluster.Labels[constants.LabelBizType]
		desiredRule.Labels[constants.LabelProjectID] = t.Cluster.Labels[constants.LabelProjectID]
		desiredRule.Labels[constants.LabelClusterID] = t.Cluster.Labels[constants.LabelClusterID]
		desiredRule.Labels[constants.LabelApplicationID] = t.Cluster.Labels[constants.LabelApplicationID]
		if t.Cluster.Status.Phase == constants.PAUSING {
			desiredRule.Labels[constants.LabelClusterPaused] = "true"
		} else {
			delete(desiredRule.Labels, constants.LabelClusterPaused)
		}

		desiredRule.Spec = mergedSpec
		return nil
	}); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *VMRule) Delete(ctx context.Context) deploy.Result {
	rule := &vmv1beta1.VMRule{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.Cluster.Name,
			Namespace: t.Cluster.Namespace,
		},
	}
	err := utils.EnsureObjectDeleted(ctx, t.Remote, rule)
	if err != nil {
		return deploy.Error(err)
	}
	overlayRule := &vmv1beta1.VMRule{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf(constants.OVERLAY_NAME_TEMPATE, t.Cluster.Name),
			Namespace: t.Cluster.Namespace,
		},
	}
	return deploy.ErrorOrDone(utils.EnsureObjectDeleted(ctx, t.Remote, overlayRule))
}

func alertRuleMerge(templateRules, overlayRules vmv1beta1.VMRuleSpec, cluster *o11yv1.Cluster) vmv1beta1.VMRuleSpec {
	ruleSpec := vmv1beta1.VMRuleSpec{}
	ruleSpec.Groups = make([]vmv1beta1.RuleGroup, 0)

	overlayRulesInfo := make(map[string]vmv1beta1.Rule)

	// get overlayRule info if present
	for _, ruleGroup := range overlayRules.Groups {
		if ruleGroup.Rules == nil {
			continue
		}
		for _, rule := range ruleGroup.Rules {
			if rule.Labels == nil {
				continue
			}
			ruleID := fmt.Sprintf(constants.RULEID_TEMPLATE, ruleGroup.Name,
				rule.Labels[constants.RuleLabelID])
			overlayRulesInfo[ruleID] = rule
		}
	}

	vmTenantId, vmProjectId := cluster.Spec.VictoriaAccount.AccountID, cluster.Spec.VictoriaAccount.ProjectID
	// merge the overlay rule with the external rules in the rule template
	for _, g := range templateRules.Groups {
		mergedRules := make([]vmv1beta1.Rule, 0)
		if g.Rules == nil {
			continue
		}
		for _, rule := range g.Rules {
			if rule.Labels != nil {
				ruleID := fmt.Sprintf(constants.RULEID_TEMPLATE, g.Name,
					rule.Labels[constants.RuleLabelID])
				if overlayRule, ok := overlayRulesInfo[ruleID]; ok {
					modifiedTag, _ := strconv.Atoi(overlayRule.Labels[constants.RuleLabelUserModified])
					if modifiedTag > 0 {
						if modifiedTag&constants.RuleStatusModifiedMask > 0 {
							rule.Labels[constants.RuleLabelStatus] = overlayRule.Labels[constants.RuleLabelStatus]
						}
						if modifiedTag&constants.RuleThresholdModifiedMask > 0 {
							ruleExpr := structs.GenerateOverLayAlertRuleEXPR(rule.Expr)
							overlayRuleExpr := structs.GenerateOverLayAlertRuleEXPR(overlayRule.Expr)
							decimalVal := decimal.NewFromFloat(overlayRuleExpr.Value)
							rule.Expr = ruleExpr.Expr + ruleExpr.Operator + decimalVal.String()
						}
						if modifiedTag&constants.RuleForModifiedMask > 0 {
							rule.For = overlayRule.For
						}
					}
				}
				if rule.Labels[constants.RuleLabelStatus] == string(constants.RULE_DISABLED) {
					continue
				}
				if rule.Labels[constants.RuleLabelVisibility] == "external" {
					rule = replaceExternalRulePlaceHolder(rule)

					delete(rule.Labels, constants.RuleLabelCanEdit)
					delete(rule.Labels, constants.RuleLabelOperator)
					delete(rule.Labels, constants.RuleLabelMonitortarget)
					delete(rule.Labels, constants.RuleLabelStatus)
				}
			}
			mergedRules = append(mergedRules, rule)
		}
		group := vmv1beta1.RuleGroup{
			Name:        g.Name,
			Interval:    g.Interval,
			Rules:       mergedRules,
			Concurrency: constants.VMRuleGroupDefaultConcurrency,
			Tenant:      fmt.Sprintf("%d:%d", vmTenantId, vmProjectId),
			Labels: map[string]string{
				constants.ExternalLabelBizType:       cluster.Labels[constants.LabelBizType],
				constants.ExternalLabelRegion:        fmt.Sprintf("%s-%s", cluster.Labels[constants.LabelVendor], cluster.Labels[constants.LabelRegion]),
				constants.ExternalLabelTenantID:      cluster.ObjectMeta.Labels[constants.LabelTenantID],
				constants.ExternalLabelTiDBClusterID: cluster.ObjectMeta.Labels[constants.LabelApplicationID],
			},
		}
		if cluster.Labels[constants.LabelProjectID] != "" {
			group.Labels[constants.ExternalLabelProjectID] = cluster.ObjectMeta.Labels[constants.LabelProjectID]
		}
		ruleSpec.Groups = append(ruleSpec.Groups, group)
	}
	return ruleSpec
}

func replaceExternalRulePlaceHolder(externalrule vmv1beta1.Rule) vmv1beta1.Rule {
	overlayExpr := structs.GenerateOverLayAlertRuleEXPR(externalrule.Expr)
	minu := decimal.NewFromFloat(overlayExpr.Value)
	mul := decimal.NewFromFloat(100)
	thresholdPercentage := fmt.Sprintf("%v", minu.Mul(mul))
	duration, _ := time.ParseDuration(externalrule.For)
	triggerInteval := fmt.Sprintf("%.0f", duration.Minutes())
	placeHolderVariables := map[string]string{
		constants.PlaceHolderThreshold:           fmt.Sprintf("%v", overlayExpr.Value),
		constants.PlaceHolderSeverity:            externalrule.Labels[constants.RuleLabelSeverity],
		constants.PlaceHolderTriggerInteval:      triggerInteval,
		constants.PlaceHolderThresholdPercentage: thresholdPercentage,
	}

	if externalrule.Annotations == nil {
		externalrule.Annotations = make(map[string]string)
	}
	if externalrule.Labels == nil {
		externalrule.Labels = make(map[string]string)
	}

	externalrule.Annotations[constants.RuleAnnotationsMessage] = replaceByPlaceHold(externalrule.Annotations[constants.RuleAnnotationsMessage],
		placeHolderVariables)
	externalrule.Annotations[constants.RuleAnnotationsDescription] = replaceByPlaceHold(externalrule.Annotations[constants.RuleAnnotationsDescription],
		placeHolderVariables)
	externalrule.Annotations[constants.RuleAnnotationsSummary] = replaceByPlaceHold(externalrule.Annotations[constants.RuleAnnotationsSummary],
		placeHolderVariables)
	externalrule.Labels[constants.RuleAnnotationsMessage] = replaceByPlaceHold(externalrule.Labels[constants.RuleAnnotationsMessage],
		placeHolderVariables)
	externalrule.Labels[constants.RuleAnnotationsDescription] = replaceByPlaceHold(externalrule.Labels[constants.RuleAnnotationsDescription],
		placeHolderVariables)

	externalrule.Alert = replaceByPlaceHold(externalrule.Alert,
		placeHolderVariables)
	return externalrule
}

func replaceByPlaceHold(src string, placeholderVariables map[string]string) string {
	replaceConfig := make([]string, 0)
	for _, hold := range constants.Placeholder {
		if place, ok := placeholderVariables[hold]; ok {
			replaceConfig = append(replaceConfig,
				constants.PlaceholderPrefix+hold+constants.PlaceholderSufix,
				place)
		}
	}
	replaceMent := strings.NewReplacer(replaceConfig...)
	return replaceMent.Replace(src)
}
