package resourcemanager

import (
	"context"
	"testing"

	"github.com/tidbcloud/observability/pkg/common/configs"
	"github.com/tidbcloud/observability/pkg/common/structs"

	prometheusv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	"github.com/tidbcloud/observability/pkg/common/errors"

	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/observability/pkg/common/constants"
)

func mockSharedPrometheusRule(id string, tenantID, projectID string) *prometheusv1.PrometheusRule {
	return &prometheusv1.PrometheusRule{
		ObjectMeta: metav1.ObjectMeta{
			Name:      id,
			Namespace: "default-" + tenantID,
			Labels: map[string]string{
				constants.LabelClusterID:     id,
				constants.LabelVendor:        "aws",
				constants.LabelRegion:        "us-east-2",
				constants.LabelTenantID:      tenantID,
				constants.LabelProjectID:     projectID,
				constants.LabelApplicationID: "default",
				constants.LabelBizType:       "default",
				constants.LabelShareMode:     "shared",
				constants.LabelVMAccountID:   "10",
			},
			Annotations: map[string]string{
				constants.AnnotationNote: "note111",
			},
		},
		Spec: prometheusv1.PrometheusRuleSpec{
			Groups: []prometheusv1.RuleGroup{
				0: {
					Name:     "example",
					Interval: "5",
					Rules: []prometheusv1.Rule{
						{
							Alert: "HighRequestLatency",
							Expr:  intstr.FromString("request_latency_seconds:mean5m{job=\"myjob\"} > 0.5"),
							For:   "10m",
							Labels: map[string]string{
								"severity": "critical",
							},
							Annotations: map[string]string{
								"summary": "High request latency",
							},
						},
					},
				},
			},
		},
	}
}

func Test_CreateAndDeleteVMRule(t *testing.T) {
	configs.Init(structs.ServiceInfo{
		BizTypeConfigs: map[string]structs.BizTypeConfig{
			"default": {
				BizType:          "default",
				MultiTenancyRule: true,
			},
		},
	})
	vmRuleRW.Create(context.TODO(), mockSharedPrometheusRule(defaultClusterID, "default", "defaultProject"))
	defer vmRuleRW.DeletePrometheusRules(context.TODO(), defaultClusterID)
	clusterID := "newCluster"
	_, err := vmRuleRW.GetPrometheusRule(context.TODO(), clusterID)
	assert.Error(t, err)
	assert.Equal(t, errors.PrometheusRuleNotFound, err.(errors.Error).Code())

	exists, err := vmRuleRW.IsPrometheusRuleIDExists(context.TODO(), clusterID)
	assert.NoError(t, err)
	assert.False(t, exists)

	exists, err = vmRuleRW.IsPrometheusRuleIDExists(context.TODO(), defaultClusterID)
	assert.NoError(t, err)
	assert.True(t, exists)

	t.Run("no project id", func(t *testing.T) {
		data, err := vmRuleRW.GetPrometheusRule(context.TODO(), defaultClusterID)
		assert.NoError(t, err)
		assert.Equal(t, data.Labels[constants.LabelVMAccountID], "10")
		assert.Empty(t, data.Labels[constants.LabelVMProjectID])
	})

	t.Run("project id", func(t *testing.T) {
		rule := mockSharedPrometheusRule("clusterid", "default", "project")
		rule.Labels[constants.LabelVMProjectID] = "1"
		vmRuleRW.Create(context.TODO(), rule)
		defer vmRuleRW.DeletePrometheusRules(context.TODO(), "clusterid")
		data, err := vmRuleRW.GetPrometheusRule(context.TODO(), "clusterid")
		assert.NoError(t, err)
		assert.Equal(t, data.Labels[constants.LabelVMAccountID], "10")
		assert.Equal(t, data.Labels[constants.LabelVMProjectID], "1")
	})
}

func Test_UpdateVMRule(t *testing.T) {
	vmRuleRW.Create(context.TODO(), mockSharedPrometheusRule("updateID", "default", "updateID"))
	defer vmRuleRW.DeletePrometheusRules(context.TODO(), "updateID")

	result, err := vmRuleRW.GetPrometheusRule(context.TODO(), "updateID")
	assert.NoError(t, err)
	assert.Equal(t, "updateID", result.Labels[constants.LabelProjectID])
	assert.Equal(t, "note111", result.Annotations[constants.AnnotationNote])

	result.Annotations[constants.AnnotationNote] = "newNote"

	err = vmRuleRW.UpdatePrometheusRule(context.TODO(), result)
	assert.NoError(t, err)

	result, err = vmRuleRW.GetPrometheusRule(context.TODO(), "updateID")
	assert.NoError(t, err)
	assert.Equal(t, "updateID", result.Labels[constants.LabelProjectID])
	assert.Equal(t, "newNote", result.Annotations[constants.AnnotationNote])
}

func Test_QueryVMRule(t *testing.T) {
	promRule1 := mockSharedPrometheusRule("cluster1", "default", "project1")
	vmRuleRW.Create(context.TODO(), promRule1)
	defer vmRuleRW.DeletePrometheusRules(context.TODO(), "cluster1")

	t.Run("query", func(t *testing.T) {
		result, err := vmRuleRW.GetPrometheusRule(context.TODO(), "cluster1")
		assert.NoError(t, err)
		assert.Equal(t, 1, len(result.Spec.Groups))
	})

}

func Test_prometheusRuleToVMRule(t *testing.T) {
	t.Run("no account id", func(t *testing.T) {
		resource := mockSharedPrometheusRule("clusterid", "tenantid", "projectid")
		delete(resource.Labels, constants.LabelVMAccountID)
		vmrule := prometheusRuleToVMRule(resource)
		assert.Equal(t, len(resource.Spec.Groups), len(vmrule.Spec.Groups))
		assert.Empty(t, vmrule.Labels[constants.LabelVMAccountID])
		assert.Empty(t, vmrule.Labels[constants.LabelVMProjectID])
		assert.Empty(t, vmrule.Spec.Groups[0].Labels)
		assert.Empty(t, vmrule.Spec.Groups[0].Tenant)
	})
	t.Run("no project id", func(t *testing.T) {
		resource := mockSharedPrometheusRule("clusterid", "tenantid", "projectid")
		vmrule := prometheusRuleToVMRule(resource)
		assert.Equal(t, len(resource.Spec.Groups), len(vmrule.Spec.Groups))
		assert.Equal(t, "10", vmrule.Labels[constants.LabelVMAccountID])
		assert.Empty(t, vmrule.Labels[constants.LabelVMProjectID])
		assert.NotEmpty(t, vmrule.Spec.Groups[0].Labels)
		assert.Equal(t, "tenantid", vmrule.Spec.Groups[0].Labels[constants.ExternalLabelTenantID])
		assert.Equal(t, "default", vmrule.Spec.Groups[0].Labels[constants.ExternalLabelTiDBClusterID])
		assert.Equal(t, resource.Labels[constants.LabelProjectID], vmrule.Spec.Groups[0].Labels[constants.ExternalLabelProjectID])
		assert.Equal(t, "10", vmrule.Spec.Groups[0].Tenant)
	})

	t.Run("account id, project id", func(t *testing.T) {
		resource := mockSharedPrometheusRule("clusterid", "tenantid", "projectid")
		resource.Labels[constants.LabelVMProjectID] = "0"
		vmrule := prometheusRuleToVMRule(resource)
		assert.Equal(t, len(resource.Spec.Groups), len(vmrule.Spec.Groups))
		assert.Equal(t, "10", vmrule.Labels[constants.LabelVMAccountID])
		assert.Equal(t, "0", vmrule.Labels[constants.LabelVMProjectID])
		assert.NotEmpty(t, vmrule.Spec.Groups[0].Labels)
		assert.Equal(t, "tenantid", vmrule.Spec.Groups[0].Labels[constants.ExternalLabelTenantID])
		assert.Equal(t, "default", vmrule.Spec.Groups[0].Labels[constants.ExternalLabelTiDBClusterID])
		assert.Equal(t, resource.Labels[constants.LabelProjectID], vmrule.Spec.Groups[0].Labels[constants.ExternalLabelProjectID])
		assert.Equal(t, "10:0", vmrule.Spec.Groups[0].Tenant)
	})
}

func Test_VM_ListPrometheusRulesInNs(t *testing.T) {
	promRule1 := mockPrometheusRule("cluster1", "default", "project1")
	vmRuleRW.Create(context.TODO(), promRule1)
	defer vmRuleRW.DeletePrometheusRules(context.TODO(), "cluster1")

	t.Run("list", func(t *testing.T) {
		result, err := vmRuleRW.ListPrometheusRulesInNs(context.TODO(), "default-default")
		assert.NoError(t, err)
		assert.Equal(t, 1, len(result[0].Spec.Groups))
	})

	t.Run("empty", func(t *testing.T) {
		result, err := vmRuleRW.ListPrometheusRulesInNs(context.TODO(), "default-empty")
		assert.NoError(t, err)
		assert.Equal(t, 0, len(result))
	})

}
