package resourcemanager

import (
	"context"
	"fmt"

	vm "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	prometheusv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type vmRuleReaderWriter struct {
	cli client.Client
}

func vmRuleToPrometheusRule(data *vm.VMRule) *prometheusv1.PrometheusRule {
	promSpec := prometheusv1.PrometheusRuleSpec{}
	if data.Spec.Groups != nil {
		promSpec.Groups = make([]prometheusv1.RuleGroup, 0)
		for _, v := range data.Spec.Groups {
			vmRules := make([]prometheusv1.Rule, 0)
			if v.Rules != nil {
				for _, rule := range v.Rules {
					vmRule := prometheusv1.Rule{
						Record:      rule.Record,
						Alert:       rule.Alert,
						Expr:        intstr.FromString(rule.Expr),
						For:         rule.For,
						Labels:      rule.Labels,
						Annotations: rule.Annotations,
					}
					vmRules = append(vmRules, vmRule)
				}
			}
			group := prometheusv1.RuleGroup{
				Name:     v.Name,
				Interval: v.Interval,
				Rules:    vmRules,
			}
			promSpec.Groups = append(promSpec.Groups, group)
		}
	}
	return &prometheusv1.PrometheusRule{
		ObjectMeta: data.ObjectMeta,
		TypeMeta:   data.TypeMeta,
		Spec:       promSpec,
	}
}

func prometheusRuleToVMRule(data *prometheusv1.PrometheusRule) *vm.VMRule {
	vmspec := vm.VMRuleSpec{}
	vmspec.Groups = make([]vm.RuleGroup, 0)

	tenantID, hasTenantID := data.ObjectMeta.Labels[constants.LabelVMAccountID]
	if hasTenantID {
		if projectID, ok := data.ObjectMeta.Labels[constants.LabelVMProjectID]; ok {
			tenantID = fmt.Sprintf("%s:%s", tenantID, projectID)
		}
	}

	if data.Spec.Groups != nil {
		for _, v := range data.Spec.Groups {
			vmRules := make([]vm.Rule, 0)
			if v.Rules != nil {
				for _, rule := range v.Rules {
					vmRule := vm.Rule{
						Record:      rule.Record,
						Alert:       rule.Alert,
						Expr:        rule.Expr.String(),
						For:         rule.For,
						Labels:      rule.Labels,
						Annotations: rule.Annotations,
					}
					vmRules = append(vmRules, vmRule)
				}
			}
			group := vm.RuleGroup{
				Name:        v.Name,
				Interval:    v.Interval,
				Rules:       vmRules,
				Concurrency: constants.VMRuleGroupDefaultConcurrency,
			}
			if hasTenantID {
				group.Tenant = tenantID
				// add external labels
				group.Labels = map[string]string{
					constants.ExternalLabelBizType:       data.ObjectMeta.Labels[constants.LabelBizType],
					constants.ExternalLabelRegion:        fmt.Sprintf("%s-%s", data.Labels[constants.LabelVendor], data.Labels[constants.LabelRegion]),
					constants.ExternalLabelTenantID:      data.ObjectMeta.Labels[constants.LabelTenantID],
					constants.ExternalLabelTiDBClusterID: data.ObjectMeta.Labels[constants.LabelApplicationID],
				}
				if data.ObjectMeta.Labels[constants.LabelProjectID] != "" {
					group.Labels[constants.ExternalLabelProjectID] = data.ObjectMeta.Labels[constants.LabelProjectID]
				}
			}
			vmspec.Groups = append(vmspec.Groups, group)
		}
	}
	return &vm.VMRule{
		ObjectMeta: data.ObjectMeta,
		TypeMeta:   data.TypeMeta,
		Spec:       vmspec,
	}
}

func (p vmRuleReaderWriter) Create(ctx context.Context, data *prometheusv1.PrometheusRule) error {
	//vmrule.ObjectMeta.Labels[constants.LabelMultiTenancyRule] = strconv.FormatBool(LabelMultiTenancyRule)
	return WrapClientError(p.cli.Create(ctx, prometheusRuleToVMRule(data)))
}

func (p vmRuleReaderWriter) IsPrometheusRuleIDExists(ctx context.Context, id string) (bool, error) {
	vmRules := new(vm.VMRuleList)
	matchingLabels := client.MatchingLabels{
		constants.LabelClusterID: id,
	}

	err := p.cli.List(ctx, vmRules,
		matchingLabels,
	)

	if err == nil && len(vmRules.Items) > 0 {
		return true, nil
	}

	return false, WrapClientError(err)
}

func (p vmRuleReaderWriter) UpdatePrometheusRule(ctx context.Context, data *prometheusv1.PrometheusRule) error {
	return WrapClientError(p.cli.Update(ctx, prometheusRuleToVMRule(data)))
}

func (p vmRuleReaderWriter) GetPrometheusRule(ctx context.Context, id string) (data *prometheusv1.PrometheusRule, err error) {
	vmRuleList := new(vm.VMRuleList)
	matchingLabels := client.MatchingLabels{
		constants.LabelClusterID: id,
	}

	err = p.cli.List(ctx, vmRuleList,
		matchingLabels,
	)

	if err == nil && len(vmRuleList.Items) == 0 {
		return nil, errors.NewErrorf(errors.PrometheusRuleNotFound, "vmRule %s is not found", id)
	}

	if err == nil && len(vmRuleList.Items) > 0 {
		return vmRuleToPrometheusRule(&vmRuleList.Items[0]), nil
	}

	return nil, WrapClientError(err)
}

func (p vmRuleReaderWriter) DeletePrometheusRules(ctx context.Context, id string) (err error) {
	vmRules, err := p.GetPrometheusRule(ctx, id)
	if err != nil {
		return err
	}
	return p.cli.Delete(ctx, &vm.VMRule{
		ObjectMeta: metav1.ObjectMeta{
			Name:      id,
			Namespace: vmRules.Namespace,
		},
	})
}

func (p vmRuleReaderWriter) ListPrometheusRulesInNs(ctx context.Context, ns string) (rules []*prometheusv1.PrometheusRule, err error) {
	vmRuleList := new(vm.VMRuleList)

	err = p.cli.List(ctx, vmRuleList,
		client.InNamespace(ns),
	)
	if err != nil {
		return nil, WrapClientError(err)
	}

	for _, vmRule := range vmRuleList.Items {
		rules = append(rules, vmRuleToPrometheusRule(&vmRule))
	}

	return rules, nil
}
