package resourcemanager

import (
	"context"
	"testing"

	"github.com/google/uuid"
	"github.com/tidbcloud/observability/pkg/common/message"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

	"github.com/tidbcloud/observability/pkg/common/constants"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/stretchr/testify/assert"
	v1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
)

func mockCluster(id string, tenantID, projectID string) *v1.Cluster {
	return &v1.Cluster{
		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: v1.ClusterSpec{
			Prometheus: v1.Prometheus{
				Name: id,
			},
			Endpoint: v1.Endpoint{
				Name:    id,
				UserARN: "userARN111",
			},
		},
		Status: v1.ClusterStatus{
			Phase: constants.ACTIVE,
		},
	}
}

var defaultClusterID = "defaultCluster"

func Test_CreateAndDelete(t *testing.T) {
	// disturbance term
	ctx := context.WithValue(context.Background(), constants.OpenAPIHeaderRequestId, uuid.New().String())
	clusterRW.CreateCluster(ctx, mockCluster(defaultClusterID, "default", "defaultProject"))
	defer clusterRW.DeleteCluster(ctx, defaultClusterID)

	clusterID := "newCluster"
	projectID := "newProject"
	_, err := clusterRW.GetCluster(ctx, clusterID)
	assert.Error(t, err)
	assert.Equal(t, errors.ClusterNotFound, err.(errors.Error).Code())

	exists, err := clusterRW.IsClusterIDExists(ctx, clusterID)
	assert.NoError(t, err)
	assert.False(t, exists)

	exists, err = clusterRW.IsClusterExists(ctx, "default", "default", projectID, "default")
	assert.NoError(t, err)
	assert.False(t, exists)

	newCluster := mockCluster(clusterID, "default", projectID)
	err = clusterRW.CreateCluster(ctx, newCluster)
	assert.NoError(t, err)

	data, err := clusterRW.GetCluster(ctx, clusterID)
	assert.NoError(t, err)
	assert.Equal(t, clusterID, data.Name)

	exists, err = clusterRW.IsClusterIDExists(ctx, clusterID)
	assert.NoError(t, err)
	assert.True(t, exists)

	exists, err = clusterRW.IsClusterExists(ctx, "default", projectID, "default", "default")
	assert.NoError(t, err)
	assert.True(t, exists)

	err = clusterRW.DeleteCluster(ctx, clusterID)
	assert.NoError(t, err)

	_, err = clusterRW.GetCluster(ctx, clusterID)
	assert.Error(t, err)
	assert.Equal(t, errors.ClusterNotFound, err.(errors.Error).Code())

	err = clusterRW.DeleteCluster(ctx, clusterID)
	assert.Error(t, err)

}

func Test_SoftDelete(t *testing.T) {
	clusterRW.CreateCluster(context.TODO(), mockCluster("SoftDelete", "SoftDelete", "defaultProject"))
	defer clusterRW.DeleteCluster(context.TODO(), "SoftDelete")
	err := clusterRW.SoftDeleteCluster(context.TODO(), "NOT_FOUND")
	assert.Error(t, err)
	err = clusterRW.SoftDeleteCluster(context.TODO(), "SoftDelete")
	assert.NoError(t, err)
	result, err := clusterRW.GetCluster(context.TODO(), "SoftDelete")
	assert.NoError(t, err)
	assert.Empty(t, result.Labels[constants.LabelTimestampToDelete])
	assert.Equal(t, constants.TO_BE_DELETED, result.Status.Phase)
}

func Test_SoftPauseSharedCluster(t *testing.T) {
	cluster := mockCluster("SoftPause", "SoftPause", "defaultProject")
	clusterRW.CreateCluster(context.TODO(), cluster)
	defer clusterRW.DeleteCluster(context.TODO(), "SoftPause")

	err := clusterRW.SoftPauseCluster(context.TODO(), cluster)
	assert.NoError(t, err)

	result, err := clusterRW.GetCluster(context.TODO(), "SoftPause")
	assert.NoError(t, err)
	assert.Equal(t, constants.TO_BE_PAUSED, result.Status.Phase)
}

func Test_SoftPauseDedicatedCluster(t *testing.T) {
	p := post
	defer func() {
		post = p
	}()

	post = func(url string) ([]byte, error) {
		return []byte("{\"status\":\"success\",\"data\":{\"name\":\"aaa\"}}"), nil
	}
	cluster := mockCluster("SoftPause2", "SoftPause2", "defaultProject")
	cluster.Annotations[constants.AnnotationShareMode] = string(constants.EXCLUSIVE)
	clusterRW.CreateCluster(context.TODO(), cluster)
	defer clusterRW.DeleteCluster(context.TODO(), "SoftPause2")

	err := clusterRW.SoftPauseCluster(context.TODO(), cluster)
	assert.NoError(t, err)

	result, err := clusterRW.GetCluster(context.TODO(), "SoftPause2")
	assert.NoError(t, err)
	assert.Equal(t, constants.TO_BE_PAUSED, result.Status.Phase)
}

func Test_SoftPauseDedicatedClusterFailed(t *testing.T) {
	p := post
	defer func() {
		post = p
	}()

	cluster := mockCluster("SoftPause3", "SoftPause3", "defaultProject")
	cluster.Annotations[constants.AnnotationShareMode] = string(constants.EXCLUSIVE)
	clusterRW.CreateCluster(context.TODO(), cluster)
	defer clusterRW.DeleteCluster(context.TODO(), "SoftPause3")

	err := clusterRW.SoftPauseCluster(context.TODO(), cluster)
	assert.Error(t, err)
	assert.Equal(t, errors.HttpClientResponseError, err.(errors.Error).Code())

	result, err := clusterRW.GetCluster(context.TODO(), "SoftPause3")
	assert.NoError(t, err)
	assert.Equal(t, constants.ACTIVE, result.Status.Phase)
}

func Test_PauseCluster(t *testing.T) {
	cluster := mockCluster("Pause", "pause", "defaultProject")
	clusterRW.CreateCluster(context.TODO(), cluster)
	defer clusterRW.DeleteCluster(context.TODO(), "Pause")

	err := clusterRW.PauseCluster(context.TODO(), cluster)
	assert.NoError(t, err)

	result, err := clusterRW.GetCluster(context.TODO(), "Pause")
	assert.NoError(t, err)
	assert.Equal(t, constants.PAUSING, result.Status.Phase)

	cluster.Name = "aaaaa"
	err = clusterRW.PauseCluster(context.TODO(), cluster)
	assert.Error(t, err)

}

func Test_ResumeCluster(t *testing.T) {
	cluster := mockCluster("Resume", "resume", "defaultProject")
	clusterRW.CreateCluster(context.TODO(), cluster)
	defer clusterRW.DeleteCluster(context.TODO(), "Resume")

	err := clusterRW.ResumeCluster(context.TODO(), cluster)
	assert.NoError(t, err)

	result, err := clusterRW.GetCluster(context.TODO(), "Resume")
	assert.NoError(t, err)
	assert.Equal(t, constants.RESUMING, result.Status.Phase)

	cluster.Name = "aaaaaaa"
	err = clusterRW.ResumeCluster(context.TODO(), cluster)
	assert.Error(t, err)
}

func Test_Update(t *testing.T) {
	clusterRW.CreateCluster(context.TODO(), mockCluster("updateID", "default", "updateID"))
	defer clusterRW.DeleteCluster(context.TODO(), "updateID")

	result, err := clusterRW.GetCluster(context.TODO(), "updateID")
	assert.NoError(t, err)
	assert.Equal(t, "updateID", result.Labels[constants.LabelProjectID])
	assert.Equal(t, "note111", result.Annotations[constants.AnnotationNote])
	assert.Equal(t, "userARN111", result.Spec.Endpoint.UserARN)

	result.Annotations[constants.AnnotationNote] = "newNote"
	result.Spec.Endpoint.UserARN = "newUser"

	err = clusterRW.UpdateCluster(context.TODO(), result)
	assert.NoError(t, err)

	result, err = clusterRW.GetCluster(context.TODO(), "updateID")
	assert.NoError(t, err)
	assert.Equal(t, "updateID", result.Labels[constants.LabelProjectID])
	assert.Equal(t, "newNote", result.Annotations[constants.AnnotationNote])
	assert.Equal(t, "newUser", result.Spec.Endpoint.UserARN)
}

func Test_Query(t *testing.T) {
	cluster1 := mockCluster("cluster1", "query", "project1")
	clusterRW.CreateCluster(context.TODO(), cluster1)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster1")

	cluster2 := mockCluster("cluster2", "query", "project2")
	clusterRW.CreateCluster(context.TODO(), cluster2)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster2")

	cluster3 := mockCluster("cluster3", "query", "project3")
	clusterRW.CreateCluster(context.TODO(), cluster3)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster3")

	cluster4 := mockCluster("cluster4", "query", "project4")
	clusterRW.CreateCluster(context.TODO(), cluster4)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster4")

	cluster5 := mockCluster("cluster5", "query", "project4")
	clusterRW.CreateCluster(context.TODO(), cluster5)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster6")

	cluster6 := mockCluster("cluster6", "query", "project4")
	clusterRW.CreateCluster(context.TODO(), cluster6)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster6")

	t.Run("filter", func(t *testing.T) {
		result, err := clusterRW.QueryClusters(context.TODO(), "query", "project4", "default", "default", message.ContinueRequest{
			Limit:         1000,
			ContinueToken: "",
		})
		assert.NoError(t, err)
		assert.Equal(t, 1, len(result.Items))
		assert.Equal(t, "project4", result.Items[0].Labels[constants.LabelProjectID])
	})

	t.Run("without label", func(t *testing.T) {
		result, err := clusterRW.QueryClusters(context.TODO(), "", "", "", "", message.ContinueRequest{})
		assert.NoError(t, err)
		assert.Equal(t, 4, len(result.Items))
	})

	t.Run("limit", func(t *testing.T) {
		result, err := clusterRW.QueryClusters(context.TODO(), "query", "project4", "", "", message.ContinueRequest{
			Limit:         2,
			ContinueToken: "aaa",
		})

		assert.NoError(t, err)
		assert.NotEmpty(t, result.Items)
		//assert.Equal(t, 2, len(result.Items))
		//assert.NotEmpty(t, result.GetContinue())
		//assert.Equal(t, int64(1), *result.GetRemainingItemCount())
	})
}

func Test_QueryClustersByRuleTemplateID(t *testing.T) {
	cluster1 := mockCluster("cluster1", "ruleTemplate", "project1")
	clusterRW.CreateCluster(context.TODO(), cluster1)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster1")

	cluster2 := mockCluster("cluster2", "ruleTemplate", "project2")
	clusterRW.CreateCluster(context.TODO(), cluster2)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster2")

	cluster3 := mockCluster("cluster3", "ruleTemplate", "project3")
	clusterRW.CreateCluster(context.TODO(), cluster3)
	defer clusterRW.DeleteCluster(context.TODO(), "cluster3")

	t.Run("suc", func(t *testing.T) {
		_, err := clusterRW.QueryClustersByRuleTemplateID(context.TODO(), "test", message.ContinueRequest{
			Limit:         1000,
			ContinueToken: "",
		})
		assert.NoError(t, err)
	})

	t.Run("templeid empty", func(t *testing.T) {
		_, err := clusterRW.QueryClustersByRuleTemplateID(context.TODO(), "", message.ContinueRequest{
			Limit:         1000,
			ContinueToken: "",
		})
		assert.NotNil(t, err)
	})

	t.Run("ContinueToken not empty", func(t *testing.T) {
		_, err := clusterRW.QueryClustersByRuleTemplateID(context.TODO(), "test2", message.ContinueRequest{
			Limit:         1000,
			ContinueToken: "aaaaaa",
		})
		assert.NoError(t, err)
	})

}

func Test_ListClusters(t *testing.T) {
	cluster := mockCluster("cluster1", "t1", "project1")
	clusterRW.CreateCluster(context.TODO(), cluster)
	cs, err := clusterRW.ListClusters(context.TODO(), client.InNamespace("default-t1"))
	assert.NoError(t, err)
	assert.Equal(t, 1, len(cs.Items))
}

func Test_createSnapshotBeforeDeleting(t *testing.T) {
	p := post
	defer func() {
		post = p
	}()

	t.Run("error", func(t *testing.T) {
		err := createSnapshot(context.TODO(), &v1.Cluster{})
		assert.Error(t, err)
		assert.Equal(t, errors.HttpClientResponseError, err.(errors.Error).Code())
	})
	t.Run("Unmarshal err", func(t *testing.T) {
		post = func(url string) ([]byte, error) {
			return []byte{}, nil
		}
		err := createSnapshot(context.TODO(), &v1.Cluster{})
		assert.Error(t, err)
		assert.Equal(t, errors.UnmarshalError, err.(errors.Error).Code())
	})
	t.Run("Unmarshal err", func(t *testing.T) {
		post = func(url string) ([]byte, error) {
			return []byte("{\"status\":\"success\",\"data\":{\"name\":\"\"}}"), nil
		}
		err := createSnapshot(context.TODO(), &v1.Cluster{})
		assert.Error(t, err)
		assert.Equal(t, errors.UnrecognizedError, err.(errors.Error).Code())
	})
	t.Run("normal", func(t *testing.T) {
		post = func(url string) ([]byte, error) {
			return []byte("{\"status\":\"success\",\"data\":{\"name\":\"aaa\"}}"), nil
		}
		cluster := &v1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: "ssssss",
				Annotations: map[string]string{
					"specID": "default",
				},
			},
		}
		err := createSnapshot(context.TODO(), cluster)
		assert.NoError(t, err)
		assert.Equal(t, "aaa", cluster.Annotations[constants.AnnotationNewDataSnapshot])
	})
}
