package regionalservice

import (
	"context"
	"testing"

	prometheusv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	"github.com/stretchr/testify/assert"
	workerv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/utils"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"

	informers "github.com/prometheus-operator/prometheus-operator/pkg/client/informers/externalversions"
	"github.com/tidbcloud/observability/pkg/common/configs"

	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/errors"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/resourcemanager"
	"github.com/tidbcloud/observability/test/mockresourcemanager"
	"go.uber.org/mock/gomock"
)

func TestRuleSync_dispatch(t *testing.T) {
	alertRuleSyncConfig = structs.SyncAlertRuleConfig{
		FailedRetryTimes: 3,
	}
	worker = workers{
		maxWorkers:         8,
		jobChannel:         make(chan job, 1000),
		jobResponseChannel: make(chan jobResponse),
	}
	worker.start()

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	promeRuleRW := mockresourcemanager.NewMockPrometheusRuleReaderWriter(ctrl)
	resourcemanager.MockPrometheusRuleReaderWriter(promeRuleRW)
	clusterRW := mockresourcemanager.NewMockClusterReaderWriter(ctrl)
	resourcemanager.MockClusterReaderWriter(clusterRW)

	dataAccountRW := mockresourcemanager.NewMockDataAccountReaderWriter(ctrl)
	resourcemanager.MockDataAccountReaderWriter(dataAccountRW)

	dataAccountRW.EXPECT().UniqueInt32ForCluster(gomock.Any()).Return(int32(1212), nil).AnyTimes()

	t.Run("failed break", func(t *testing.T) {

		clusterRW.EXPECT().QueryClustersByRuleTemplateID(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New(errors.UnrecognizedError, "")).Times(1)
		promrule := mockPrometheusRule("id", "test", "test")
		promrule.Namespace = "default"
		dispatch(context.TODO(), promrule, constants.SyncCreate)
	})
	t.Run("create suc", func(t *testing.T) {
		req1 := mockCreateReq()
		req1.ID = utils.GenerateID()
		cluster1, _ := BuildClusterResourceRequirements(context.TODO(), req1, "", nil)
		promrule := mockPrometheusRule("id", "test", "test")
		promrule.Namespace = "default"

		clusterRW.EXPECT().QueryClustersByRuleTemplateID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&workerv1.ClusterList{ListMeta: metav1.ListMeta{Continue: ""}, Items: []workerv1.Cluster{*cluster1}}, nil).Times(1)
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), nil).Times(2)
		promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(nil).Times(1)
		dispatch(context.TODO(), promrule, constants.SyncCreate)
	})

	t.Run("create failed", func(t *testing.T) {
		req1 := mockCreateReq()
		req1.ID = utils.GenerateID()
		cluster1, _ := BuildClusterResourceRequirements(context.TODO(), req1, "", nil)
		promrule := mockPrometheusRule("id", "test", "test")
		promrule.Namespace = "default"

		clusterRW.EXPECT().QueryClustersByRuleTemplateID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&workerv1.ClusterList{ListMeta: metav1.ListMeta{Continue: ""}, Items: []workerv1.Cluster{*cluster1}}, nil).Times(1)
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), nil).Times(8)
		promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(errors.New(errors.UnrecognizedError, "unrecognized error")).Times(4)
		dispatch(context.TODO(), promrule, constants.SyncCreate)
	})

	t.Run("create query failed", func(t *testing.T) {
		req1 := mockCreateReq()
		req1.ID = utils.GenerateID()
		cluster1, _ := BuildClusterResourceRequirements(context.TODO(), req1, "", nil)
		promrule := mockPrometheusRule("id", "test", "test")
		promrule.Namespace = "default"

		clusterRW.EXPECT().QueryClustersByRuleTemplateID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&workerv1.ClusterList{ListMeta: metav1.ListMeta{Continue: ""}, Items: []workerv1.Cluster{*cluster1}}, nil).Times(1)
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), errors.New(errors.UnrecognizedError, "unrecognized error")).Times(4)
		//promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(errors.New(errors.UnrecognizedError, "unrecognized error")).Times(4)
		dispatch(context.TODO(), promrule, constants.SyncCreate)
	})

	t.Run("update succ", func(t *testing.T) {
		promrule := mockPrometheusRule("id", "test", "test")
		promrule.Namespace = "default"
		req1 := mockCreateReq()
		req1.ID = utils.GenerateID()
		cluster1, _ := BuildClusterResourceRequirements(context.TODO(), req1, "", nil)

		req2 := mockCreateReq()
		req2.ID = utils.GenerateID()
		cluster2, _ := BuildClusterResourceRequirements(context.TODO(), req2, "", nil)
		clusterRW.EXPECT().QueryClustersByRuleTemplateID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&workerv1.ClusterList{ListMeta: metav1.ListMeta{Continue: ""}, Items: []workerv1.Cluster{*cluster1, *cluster2}}, nil).Times(1)
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), nil).Times(4)
		promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(nil).Times(2)
		dispatch(context.TODO(), promrule, constants.SyncUpdate)
	})

	t.Run("update failed", func(t *testing.T) {
		promrule := mockPrometheusRule("id", "test", "test")
		promrule.Namespace = "default"
		req1 := mockCreateReq()
		req1.ID = utils.GenerateID()
		cluster1, _ := BuildClusterResourceRequirements(context.TODO(), req1, "", nil)

		clusterRW.EXPECT().QueryClustersByRuleTemplateID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&workerv1.ClusterList{ListMeta: metav1.ListMeta{Continue: ""}, Items: []workerv1.Cluster{*cluster1}}, nil).Times(1)
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), nil).Times(8)
		promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(errors.New(errors.UnrecognizedError, "unrecognized error")).Times(4)
		dispatch(context.TODO(), promrule, constants.SyncUpdate)
	})

	t.Run("update overlay rule succ", func(t *testing.T) {
		promrule := mockPrometheusRule("id", "test", "test")
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), nil).Times(2)
		promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(nil).Times(1)
		dispatch(context.TODO(), promrule, constants.SyncUpdate)
	})

	t.Run("update overlay rule failed", func(t *testing.T) {
		promrule := mockPrometheusRule("id", "test", "test")
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), nil).Times(8)
		promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(errors.New(errors.UnrecognizedError, "unrecognized error")).Times(4)
		dispatch(context.TODO(), promrule, constants.SyncUpdate)
	})

	t.Run("get overlay rule failed", func(t *testing.T) {
		promrule := mockPrometheusRule("id", "test", "test")
		promeRuleRW.EXPECT().GetPrometheusRule(gomock.Any(), gomock.Any()).Return(mockPrometheusRule("id", "test", "test"), errors.New(errors.UnrecognizedError, "unrecognized error")).Times(4)
		//promeRuleRW.EXPECT().UpdatePrometheusRule(gomock.Any(), gomock.Any()).Return(errors.New(errors.UnrecognizedError, "unrecognized error")).Times(4)
		dispatch(context.TODO(), promrule, constants.SyncUpdate)
	})
}

func TestRuleSync_alertRuleMerge(t *testing.T) {

	t.Run("merge succ", func(t *testing.T) {
		promrule := mockOverlayPrometheusRule("id", "test", "test")
		overlayRule := mockOverlayPrometheusRule("id", "test", "test")
		res := alertRuleMerge(promrule.Spec, overlayRule.Spec)
		assert.Empty(t, res.Groups[0].Rules[0].Labels["monitorTarget"])
	})

	t.Run("rule disabled", func(t *testing.T) {
		promrule := mockOverlayPrometheusRule("id", "test", "test")
		overlayRule := mockOverlayPrometheusRule("id", "test", "test")
		overlayRule.Spec.Groups[0].Rules[0].Labels["status"] = "disabled"
		res := alertRuleMerge(promrule.Spec, overlayRule.Spec)
		assert.Equal(t, len(res.Groups[0].Rules), 0)
	})

	t.Run("rule empty", func(t *testing.T) {
		promrule := mockOverlayPrometheusRule("id", "test", "test")
		overlayRule := mockOverlayPrometheusRule("id", "test", "test")
		promrule.Spec.Groups[0].Rules = nil
		res := alertRuleMerge(promrule.Spec, overlayRule.Spec)
		assert.Equal(t, len(res.Groups), 0)
	})

	t.Run("overlay rule empty", func(t *testing.T) {
		promrule := mockOverlayPrometheusRule("id", "test", "test")
		overlayRule := mockOverlayPrometheusRule("id", "test", "test")
		overlayRule.Spec.Groups[0].Rules = nil
		res := alertRuleMerge(promrule.Spec, overlayRule.Spec)
		assert.Equal(t, len(res.Groups), 1)
	})

}

func TestStartSync(t *testing.T) {
	temp := start
	defer func() {
		start = temp
	}()
	start = func(fa informers.SharedInformerFactory, stop chan struct{}) {
	}
	StartSync(context.TODO(), structs.SyncAlertRuleConfig{}, configs.GetServiceInfo())
}

func mockOverlayPrometheusRule(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",
			},
			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.3"),
							For:   "10m",
							Labels: map[string]string{
								"severity":     "critical",
								"alerttype":    "customer",
								"ruleID":       "1",
								"userModified": "7",
								"visibility":   "external",
							},
							Annotations: map[string]string{
								"summary":                            "High request latency",
								constants.RuleAnnotationsDescription: "Test",
							},
						},
					},
				},
			},
		},
	}
}
