package interceptor

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/tiinf/sdk/rbac/common"
	"github.com/tidbcloud/tiinf/sdk/rbac/helperimpl"

	"github.com/tidbcloud/observability/pkg/globalservice/structs"
	mock_meta_manager "github.com/tidbcloud/observability/test/mockmetamanager"
	"go.uber.org/mock/gomock"
)

func Test_GetPermissionChecker(t *testing.T) {
	ctrl := gomock.NewController(t)
	cacheGetter := mock_meta_manager.NewMockMetaCacheGetter(ctrl)
	cacheGetter.EXPECT().GetGlobalMetaCache().Return(
		&structs.GlobalServiceMeta{
			ServerConfig: structs.ServerConfig{
				BizToAccountProviderSvcAddr: map[string]string{
					"tidbcloud":     "test:10000",
					"dev-tidbcloud": "test-dev:10000",
				}},
		},
	).AnyTimes()
	metaCacheGetter = cacheGetter
	t.Run("unsupported bizType", func(t *testing.T) {
		_, err := getPermissionChecker("tidbcloud-dev")
		assert.NotNil(t, err)
	})

	t.Run("getPermissionChecker", func(t *testing.T) {
		checker, err := getPermissionChecker("tidbcloud")
		assert.Nil(t, err)
		assert.NotNil(t, checker)
		checker2, err := getPermissionChecker("tidbcloud")
		assert.Nil(t, err)
		assert.Equal(t, checker, checker2)
		checker3, err := getPermissionChecker("dev-tidbcloud")
		assert.Nil(t, err)
		assert.NotEqual(t, checker, checker3)
	})
}

func Test_GenAccessParamWithCurrentProject(t *testing.T) {
	gin.SetMode(gin.TestMode)

	t.Run("replace project id from header for org project cluster path", func(t *testing.T) {
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		req, err := http.NewRequest(http.MethodGet, "/api/v1/external/tidbcloud/orgs/100/projects/400/clusters/200/alerts", nil)
		assert.NoError(t, err)
		req.Header.Set("x-current-project-id", "300")
		c.Request = req

		param := GenAccessParamWithCurrentProject(c)
		path, ok := param.Metadata[common.METADATA_PATH].(string)
		assert.True(t, ok)
		assert.Equal(t, "/api/v1/external/tidbcloud/orgs/100/projects/300/clusters/200/alerts", path)
	})

	t.Run("keep path when project header not provided", func(t *testing.T) {
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		req, err := http.NewRequest(http.MethodGet, "/api/v1/external/tidbcloud/orgs/100/projects/400/clusters/200/alerts", nil)
		assert.NoError(t, err)
		c.Request = req

		param := GenAccessParamWithCurrentProject(c)
		path, ok := param.Metadata[common.METADATA_PATH].(string)
		assert.True(t, ok)
		assert.Equal(t, "/api/v1/external/tidbcloud/orgs/100/projects/400/clusters/200/alerts", path)
	})

	t.Run("keep path for org cluster route", func(t *testing.T) {
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		req, err := http.NewRequest(http.MethodGet, "/api/v1/external/tidbcloud/orgs/100/clusters/200/alerts", nil)
		assert.NoError(t, err)
		req.Header.Set("x-current-project-id", "300")
		c.Request = req

		param := GenAccessParamWithCurrentProject(c)
		path, ok := param.Metadata[common.METADATA_PATH].(string)
		assert.True(t, ok)
		assert.Equal(t, "/api/v1/external/tidbcloud/orgs/100/clusters/200/alerts", path)
	})

	t.Run("rewrite byoc getCluster metadata path for org-scoped RBAC", func(t *testing.T) {
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		req, err := http.NewRequest(http.MethodGet, "/api/v1/external/byoc/orgs/100/clusters/dp-200/", nil)
		assert.NoError(t, err)
		c.Request = req
		c.Params = gin.Params{{Key: "bizType", Value: "byoc"}}

		param := GenAccessParamWithCurrentProject(c)
		path, ok := param.Metadata[common.METADATA_PATH].(string)
		assert.True(t, ok)
		assert.Equal(t, "/api/v1/external/byoc/orgs/100/byocDataplane/dp-200", path)
	})

	t.Run("keep non-byoc getCluster metadata path unchanged", func(t *testing.T) {
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		req, err := http.NewRequest(http.MethodGet, "/api/v1/external/tidbcloud-nextgen/orgs/100/clusters/200/", nil)
		assert.NoError(t, err)
		c.Request = req
		c.Params = gin.Params{{Key: "bizType", Value: "tidbcloud-nextgen"}}

		param := GenAccessParamWithCurrentProject(c)
		path, ok := param.Metadata[common.METADATA_PATH].(string)
		assert.True(t, ok)
		assert.Equal(t, "/api/v1/external/tidbcloud-nextgen/orgs/100/clusters/200/", path)
	})
}

func Test_ByocGetClusterMapsToOrgScopedResource(t *testing.T) {
	gin.SetMode(gin.TestMode)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	req, err := http.NewRequest(http.MethodGet, "/api/v1/external/byoc/orgs/100/clusters/dp-200/", nil)
	assert.NoError(t, err)
	c.Request = req
	c.Params = gin.Params{{Key: "bizType", Value: "byoc"}}

	param := GenAccessParamWithCurrentProject(c)
	resourceHelper := helperimpl.NewMapStoreResourceHelper(pathPatternResourceMap)
	resource, err := resourceHelper.GetResource(context.Background(), param)
	assert.NoError(t, err)
	assert.Equal(t, "o11y/orgs/100/byoc/get", resource)
}

func Test_ByocObservabilityVcpuMapsToOrgScopedResource(t *testing.T) {
	gin.SetMode(gin.TestMode)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	req, err := http.NewRequest(http.MethodGet, "/api/v1/external/byoc/orgs/100/byocVcpuPools/observabilityVcpu?start=1&end=2&step=1m", nil)
	assert.NoError(t, err)
	c.Request = req
	c.Params = gin.Params{{Key: "bizType", Value: "byoc"}}

	param := GenAccessParamWithCurrentProject(c)
	resourceHelper := helperimpl.NewMapStoreResourceHelper(pathPatternResourceMap)
	resource, err := resourceHelper.GetResource(context.Background(), param)
	assert.NoError(t, err)
	assert.Equal(t, "o11y/orgs/100/byoc/observabilityVcpu", resource)
}

func Test_ByocObservabilityVcpuDoesNotMapForNonByocBizType(t *testing.T) {
	gin.SetMode(gin.TestMode)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	req, err := http.NewRequest(http.MethodGet, "/api/v1/external/tidbcloud/orgs/100/byocVcpuPools/observabilityVcpu?start=1&end=2&step=1m", nil)
	assert.NoError(t, err)
	c.Request = req
	c.Params = gin.Params{{Key: "bizType", Value: "tidbcloud"}}

	param := GenAccessParamWithCurrentProject(c)
	resourceHelper := helperimpl.NewMapStoreResourceHelper(pathPatternResourceMap)
	resource, err := resourceHelper.GetResource(context.Background(), param)
	assert.NoError(t, err)
	assert.Equal(t, "", resource)
}

func Test_PureResourceOf(t *testing.T) {
	h := &o11yPermissionHelper{pureTemplates: buildPureTemplates(pathPatternResourceMap)}

	cases := []struct {
		name        string
		substituted string
		wantPure    string
	}{
		{
			name:        "org project cluster alerts",
			substituted: "o11y/orgs/600025/projects/123/tidbs/10144649366627346860/alerts",
			wantPure:    "o11y/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/alerts",
		},
		{
			name:        "org cluster monitor-alertrules",
			substituted: "o11y/orgs/600025/tidbs/10144649366627346860/monitor-alertrules",
			wantPure:    "o11y/orgs/{org_id}/tidbs/{cluster_id}/monitor-alertrules",
		},
		{
			name:        "org cluster get",
			substituted: "o11y/orgs/600025/tidbs/10144649366627346860/get",
			wantPure:    "o11y/orgs/{org_id}/tidbs/{cluster_id}/get",
		},
		{
			name:        "org byoc get",
			substituted: "o11y/orgs/600025/byoc/get",
			wantPure:    "o11y/orgs/{org_id}/byoc/get",
		},
		{
			name:        "org byoc observability vcpu",
			substituted: "o11y/orgs/600025/byoc/observabilityVcpu",
			wantPure:    "o11y/orgs/{org_id}/byoc/observabilityVcpu",
		},
		{
			name:        "no matching template",
			substituted: "something/unrelated/42",
			wantPure:    "",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			assert.Equal(t, tc.wantPure, h.pureResourceOf(tc.substituted))
		})
	}
}
