package regionalservice

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	v1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/errors"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/resourcemanager"
	"github.com/tidbcloud/observability/test/mockresourcemanager"
	"go.uber.org/mock/gomock"
)

func TestClusterHandler_AuthAndRouteForDS(t *testing.T) {
	t.Run("no params", func(t *testing.T) {
		ch := ClusterHandler{}
		_, err := ch.AuthAndRouteForDS(context.TODO(), message.AuthRouteReq{})
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())
	})
	t.Run("with api key", func(t *testing.T) {
		ch := ClusterHandler{}
		_, err := ch.AuthAndRouteForDS(context.TODO(), message.AuthRouteReq{
			APIKey: "aaaaa",
		})
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())
	})
	t.Run("with path", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()

		clusterRW := mockresourcemanager.NewMockClusterReaderWriter(ctrl)
		resourcemanager.MockClusterReaderWriter(clusterRW)

		clusterRW.EXPECT().QueryClusters(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&findClusters, nil).Times(1)

		ch := ClusterHandler{}
		_, err := ch.AuthAndRouteForDS(context.TODO(), message.AuthRouteReq{
			Path: "/external/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query",
		})
		assert.NoError(t, err)
	})
}

func TestClusterHandler_AuthAndRouteByPath(t *testing.T) {
	t.Run("invalid path", func(t *testing.T) {
		ch := ClusterHandler{}
		_, err := ch.AuthAndRouteForDS(context.TODO(), message.AuthRouteReq{
			Path: "/invalid/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query",
		})
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())
	})

	t.Run("query cluster error", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()

		clusterRW := mockresourcemanager.NewMockClusterReaderWriter(ctrl)
		resourcemanager.MockClusterReaderWriter(clusterRW)

		clusterRW.EXPECT().QueryClusters(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&findClusters, errors.New(errors.UnrecognizedError, "")).Times(1)

		ch := ClusterHandler{}
		_, err := ch.AuthAndRouteForDS(context.TODO(), message.AuthRouteReq{
			Path: "/external/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query",
		})
		assert.Error(t, err)
		assert.Equal(t, errors.UnrecognizedError, err.(errors.Error).Code())
	})

	t.Run("no cluster found", func(t *testing.T) {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()

		clusterRW := mockresourcemanager.NewMockClusterReaderWriter(ctrl)
		resourcemanager.MockClusterReaderWriter(clusterRW)

		clusterRW.EXPECT().QueryClusters(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&v1.ClusterList{}, nil).Times(1)

		ch := ClusterHandler{}
		_, err := ch.AuthAndRouteForDS(context.TODO(), message.AuthRouteReq{
			Path: "/external/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query",
		})
		assert.Error(t, err)
		assert.Equal(t, errors.ClusterNotFound, err.(errors.Error).Code())
	})

}

func Test_parsePath(t *testing.T) {
	t.Run("normal", func(t *testing.T) {
		k, err := parsePath("/external/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.NoError(t, err)
		assert.Equal(t, "prod-devtier", k.BizType)
		assert.Equal(t, "1111111", k.TenantID)
		assert.Equal(t, "2222222", k.ProjectID)
		assert.Equal(t, "3333333", k.ApplicationID)
	})
	t.Run("illegal path", func(t *testing.T) {
		_, err := parsePath("/external/metrics/prod-devtier/tenant/1111111/")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())
	})
	t.Run("unmatched path", func(t *testing.T) {
		_, err := parsePath("/wrong/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

		_, err = parsePath("/external/metrics//prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

		_, err = parsePath("/external/metric/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

	})
}

func Test_parsePathWithNoProject(t *testing.T) {
	t.Run("normal", func(t *testing.T) {
		k, err := parsePathWithNoProject("/external/metrics/tidbcloud/tenant/1111111/application/3333333/api/v1/query")
		assert.NoError(t, err)
		assert.Equal(t, "tidbcloud", k.BizType)
		assert.Equal(t, "1111111", k.TenantID)
		assert.Equal(t, "", k.ProjectID)
		assert.Equal(t, "3333333", k.ApplicationID)
	})
	t.Run("illegal path", func(t *testing.T) {
		_, err := parsePathWithNoProject("/external/metrics/tidbcloud/tenant/1111111/")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())
	})
	t.Run("unmatched path", func(t *testing.T) {
		_, err := parsePathWithNoProject("/wrong/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

		_, err = parsePathWithNoProject("/external/metrics//prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

		_, err = parsePathWithNoProject("/external/metric/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

	})
}

func Test_parsePathWithO11yID(t *testing.T) {
	t.Run("normal", func(t *testing.T) {
		o11yid, err := parsePathWithO11yID("/internal/v2/metrics/90c5d4fa-ba99-4c40-8b5f-041f6dd28501/api/v1/query")
		assert.NoError(t, err)
		assert.Equal(t, "90c5d4fa-ba99-4c40-8b5f-041f6dd28501", o11yid)
	})
	t.Run("illegal path", func(t *testing.T) {
		_, err := parsePathWithO11yID("/external/metrics/tidbcloud/tenant/1111111/")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())
	})
	t.Run("unmatched path", func(t *testing.T) {
		_, err := parsePathWithO11yID("/wrong/metrics/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

		_, err = parsePathWithO11yID("/internal/metrics/90c5d4fa-ba99-4c40-8b5f-041f6dd28501/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

		_, err = parsePathWithO11yID("/external/metric/prod-devtier/tenant/1111111/project/2222222/application/3333333/api/v1/query")
		assert.Error(t, err)
		assert.Equal(t, errors.ParameterInvalid, err.(errors.Error).Code())

	})
}

var findClusters = v1.ClusterList{
	Items: []v1.Cluster{
		{
			Spec: v1.ClusterSpec{
				VictoriaAccount: v1.VictoriaAccount{
					VMClusterName: "test",
					AccountID:     123456,
					ProjectID:     654321,
				},
			},
		},
	},
}
