/******************************************************************************
 * Copyright (c)  2022 PingCAP                                                *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 * http://www.apache.org/licenses/LICENSE-2.0                                 *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

/*******************************************************************************
 * @File: handler_test.go
 * @Description:
 * @Author: jiangxunyu@pingcap.com
 * @Version: 1.0.0
 * @Date: 2022/6/29 14:32
*******************************************************************************/

package globalapi

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

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

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

	"github.com/Azure/go-autorest/autorest/mocks"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func Test_reqRegionalServer(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`
{
	"code": 0,
	"message": "",
	"data": {
		"clusterID": "123456"
	}
}
`))
		r.ParseForm()
	}))
	defer server.Close()

	t.Run("post json request", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodPost,
		}

		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: "aws",
				Region: "us-west-2",
			},
			Headers: map[string]string{
				"Content-Type": "application/json",
			},
			BodyParams: "{\"key\": \"value\"}",
		}

		result, err := reqRegionalServer(c, reqParams, server.URL+"/api/v1/cluster")
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
	})

	t.Run("post multipart request", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodPost,
		}

		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: "aws",
				Region: "us-west-2",
			},
			Headers: map[string]string{
				"Content-Type": "multipart/form-data",
			},
			FormParams: map[string]string{
				"key": "value",
			},
		}

		result, err := reqRegionalServer(c, reqParams, server.URL+"/api/v1/cluster")
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
	})

	t.Run("post generic request", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodPost,
		}

		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: "aws",
				Region: "us-west-2",
			},
			FormParams: map[string]string{
				"key": "value",
			},
		}

		result, err := reqRegionalServer(c, reqParams, server.URL+"/api/v1/cluster")
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
	})

	t.Run("get request", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodGet,
		}

		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: "aws",
				Region: "us-west-2",
			},
		}

		result, err := reqRegionalServer(c, reqParams, server.URL+"/api/v1/cluster?a=b&c=d")
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
	})

	t.Run("put request", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodPut,
		}

		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: "aws",
				Region: "us-west-2",
			},
			BodyParams: "{\"key\": \"value\"}",
		}

		result, err := reqRegionalServer(c, reqParams, server.URL+"/api/v1/cluster")
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
	})

	t.Run("delete request", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodDelete,
		}
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: "aws",
				Region: "us-west-2",
			},
			FormParams: map[string]string{
				"key": "value",
			},
		}

		result, err := reqRegionalServer(c, reqParams, server.URL+"/api/v1/cluster")
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
	})

	t.Run("no support request", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: "no support",
		}
		_, err := reqRegionalServer(c, structs.RequestParameters{}, server.URL+"/api/v1/cluster")
		assert.Error(t, err)
	})

	t.Run("request regional error", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodGet,
		}
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: "aws",
				Region: "us-west-2",
			},
			FormParams: map[string]string{
				"key": "value",
			},
		}

		_, err := reqRegionalServer(c, reqParams, "test://test/v1")
		assert.Error(t, err)
	})
}

func Test_resolveAndReqRegionalServer(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)

		// set context
		c.Set(structs.GlobalServerRequestParametersKey, requestParameters)

		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Path:     "/api/v1/clusters",
				RawQuery: "vendor=aws&region=us-west-2",
			},
		}

		t.Setenv("AWS_ROLE_ARN", "arn:aws:iam::1234567890:role/test")
		t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", "./handler_test.go")
		t.Setenv("AWS_REGION", "us-west-2")

		result, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
		assert.NotEmpty(t, result.Content)
	})

	t.Run("logical region routing success with exact match", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)

		// Create request parameters with logical region routing context
		// This should match "tidbcloud:12345:*" -> "us-west-2-primary"
		logicalReqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-west-2",
			},
			Headers: map[string]string{
				"vendor":                               "aws",
				"region":                               "us-west-2",
				"Auth":                                 "Bearer test_token",
				string(constants.OpenAPIHeaderBizType): "tidbcloud",
				string(constants.OpenAPIHeaderOrgId):   "12345",
			},
			QueryParams: map[string]string{
				"queryKey": "queryValue",
			},
			PathParams: []gin.Param{{
				Key:   "id",
				Value: "123456",
			}},
			FormParams: map[string]string{
				"formKey": "formValue",
			},
			BodyParams: "{\"bodyKey\": \"bodyValue\"}",
		}

		// set context with logical region parameters
		c.Set(structs.GlobalServerRequestParametersKey, logicalReqParams)

		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Path:     "/api/v1/clusters",
				RawQuery: "vendor=aws&region=us-west-2",
			},
		}

		t.Setenv("AWS_ROLE_ARN", "arn:aws:iam::1234567890:role/test")
		t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", "./handler_test.go")
		t.Setenv("AWS_REGION", "us-west-2")

		result, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
		assert.NotEmpty(t, result.Content)
	})

	t.Run("logical region routing with wildcard match", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)

		// Create request parameters that should match "tidbcloud:*:*" -> "us-west-2-secondary"
		logicalReqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-west-2",
			},
			Headers: map[string]string{
				"vendor":                               "aws",
				"region":                               "us-west-2",
				"Auth":                                 "Bearer test_token",
				string(constants.OpenAPIHeaderBizType): "tidbcloud",
				string(constants.OpenAPIHeaderOrgId):   "67890", // Different tenant ID
			},
			QueryParams: map[string]string{
				"queryKey": "queryValue",
			},
			PathParams: []gin.Param{{
				Key:   "id",
				Value: "123456",
			}},
			FormParams: map[string]string{
				"formKey": "formValue",
			},
			BodyParams: "{\"bodyKey\": \"bodyValue\"}",
		}

		// set context with logical region parameters
		c.Set(structs.GlobalServerRequestParametersKey, logicalReqParams)

		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Path:     "/api/v1/clusters",
				RawQuery: "vendor=aws&region=us-west-2",
			},
		}

		t.Setenv("AWS_ROLE_ARN", "arn:aws:iam::1234567890:role/test")
		t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", "./handler_test.go")
		t.Setenv("AWS_REGION", "us-west-2")

		result, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
		assert.NotEmpty(t, result.Content)
	})

	t.Run("logical region routing with serverless bizType", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)

		// Create request parameters that should match "serverless:*:*" -> "us-west-2-primary"
		logicalReqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-west-2",
			},
			Headers: map[string]string{
				"vendor":                               "aws",
				"region":                               "us-west-2",
				"Auth":                                 "Bearer test_token",
				string(constants.OpenAPIHeaderBizType): "serverless",
				string(constants.OpenAPIHeaderOrgId):   "any-tenant",
			},
			QueryParams: map[string]string{
				"queryKey": "queryValue",
			},
			PathParams: []gin.Param{{
				Key:   "id",
				Value: "123456",
			}},
			FormParams: map[string]string{
				"formKey": "formValue",
			},
			BodyParams: "{\"bodyKey\": \"bodyValue\"}",
		}

		// set context with logical region parameters
		c.Set(structs.GlobalServerRequestParametersKey, logicalReqParams)

		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Path:     "/api/v1/clusters",
				RawQuery: "vendor=aws&region=us-west-2",
			},
		}

		t.Setenv("AWS_ROLE_ARN", "arn:aws:iam::1234567890:role/test")
		t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", "./handler_test.go")
		t.Setenv("AWS_REGION", "us-west-2")

		result, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
		assert.NotEmpty(t, result.Content)
	})

	t.Run("logical region routing fallback to physical", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)

		// Create request parameters with incomplete logical region context
		// This should trigger fallback to physical region routing
		incompleteLogicalReqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-west-2",
			},
			Headers: map[string]string{
				"vendor": "aws",
				"region": "us-west-2",
				"Auth":   "Bearer test_token",
				// Missing bizType and tenantID headers to trigger fallback
			},
			QueryParams: map[string]string{
				"queryKey": "queryValue",
			},
			PathParams: []gin.Param{{
				Key:   "id",
				Value: "123456",
			}},
			FormParams: map[string]string{
				"formKey": "formValue",
			},
			BodyParams: "{\"bodyKey\": \"bodyValue\"}",
		}

		// set context
		c.Set(structs.GlobalServerRequestParametersKey, incompleteLogicalReqParams)

		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Path:     "/api/v1/clusters",
				RawQuery: "vendor=aws&region=us-west-2",
			},
		}

		t.Setenv("AWS_ROLE_ARN", "arn:aws:iam::1234567890:role/test")
		t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", "./handler_test.go")
		t.Setenv("AWS_REGION", "us-west-2")

		result, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
		assert.NotEmpty(t, result.Content)
	})

	t.Run("logical region disabled uses physical routing", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)

		// Create request parameters with logical region context but logical routing disabled
		logicalReqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-west-2",
			},
			Headers: map[string]string{
				"vendor":                               "aws",
				"region":                               "us-west-2",
				"Auth":                                 "Bearer test_token",
				string(constants.OpenAPIHeaderBizType): "tidbcloud",
				string(constants.OpenAPIHeaderOrgId):   "12345",
			},
			QueryParams: map[string]string{
				"queryKey": "queryValue",
			},
			PathParams: []gin.Param{{
				Key:   "id",
				Value: "123456",
			}},
			FormParams: map[string]string{
				"formKey": "formValue",
			},
			BodyParams: "{\"bodyKey\": \"bodyValue\"}",
		}

		// set context
		c.Set(structs.GlobalServerRequestParametersKey, logicalReqParams)

		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Path:     "/api/v1/clusters",
				RawQuery: "vendor=aws&region=us-west-2",
			},
		}

		t.Setenv("AWS_ROLE_ARN", "arn:aws:iam::1234567890:role/test")
		t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", "./handler_test.go")
		t.Setenv("AWS_REGION", "us-west-2")

		result, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.NoError(t, err)
		assert.Equal(t, http.StatusOK, result.HttpCode)
		assert.NotEmpty(t, result.Content)
	})

	t.Run("gen sts token error", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Scheme:   "http",
				Path:     "/api/v1?a=b&c=d",
				RawQuery: "a=b&c=d",
			},
			Header: map[string][]string{
				"Auth": {"Bearer test_token"},
			},
			RequestURI: "/api/v1/cluster",
			Body:       mocks.NewBody("{\"bodyKey\": \"testBodyValue\"}"),
		}

		_, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.Error(t, err)
	})

	t.Run("get all param error", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Method: http.MethodPost,
			URL: &url.URL{
				Scheme:   "http",
				Path:     "/api/v1?a=b&c=d",
				RawQuery: "a=b&c=d",
			},
		}

		_, err := resolveAndReqRegionalServer(c, OptionalParam{})
		assert.Error(t, err)
	})
}

func Test_handleError(t *testing.T) {
	t.Run("fmt error", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
		handleError(c, fmt.Errorf("fmt error"))
		assert.Equal(t, http.StatusInternalServerError, w.Code)
		assert.Contains(t, w.Body.String(), "fmt error")
	})

	t.Run("custom error", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
		handleError(c, errors.NewErrorf(errors.UnrecognizedError, "unrecognized"))
		assert.Equal(t, errors.UnrecognizedError.GetHttpCode(), w.Code)
		assert.Contains(t, w.Body.String(), "unrecognized")
	})
}

func Test_buildRoutingContext(t *testing.T) {
	t.Run("build routing context with headers", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-west-2",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType): "tidbcloud",
				string(constants.OpenAPIHeaderOrgId):   "12345",
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, constants.AWS, routingCtx.Vendor)
		assert.Equal(t, "us-west-2", routingCtx.Region)
		assert.Equal(t, "tidbcloud", routingCtx.BizType)
		assert.Equal(t, "12345", routingCtx.TenantID)
	})

	t.Run("build routing context with path params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.GCP,
				Region: "us-central1",
			},
			PathParams: []gin.Param{
				{Key: "bizType", Value: "serverless"},
				{Key: "tenantID", Value: "67890"},
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, constants.GCP, routingCtx.Vendor)
		assert.Equal(t, "us-central1", routingCtx.Region)
		assert.Equal(t, "serverless", routingCtx.BizType)
		assert.Equal(t, "67890", routingCtx.TenantID)
	})

	t.Run("build routing context with mixed params - headers take precedence", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.Azure,
				Region: "eastus",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType): "tidbcloud-header",
			},
			PathParams: []gin.Param{
				{Key: "bizType", Value: "serverless-path"},
				{Key: "tenantID", Value: "path-tenant"},
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, constants.Azure, routingCtx.Vendor)
		assert.Equal(t, "eastus", routingCtx.Region)
		assert.Equal(t, "tidbcloud-header", routingCtx.BizType) // Header takes precedence
		assert.Equal(t, "path-tenant", routingCtx.TenantID)     // Only available in path
	})

	t.Run("build routing context with empty params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, constants.AWS, routingCtx.Vendor)
		assert.Equal(t, "us-east-1", routingCtx.Region)
		assert.Equal(t, "", routingCtx.BizType)
		assert.Equal(t, "", routingCtx.TenantID)
	})

	t.Run("build routing context with projectID and applicationID headers", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType):       "dev-devtier",
				string(constants.OpenAPIHeaderOrgId):         "12345",
				string(constants.OpenAPIHeaderProjectID):     "f02",
				string(constants.OpenAPIHeaderApplicationID): "devtier-infra",
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, "dev-devtier", routingCtx.BizType)
		assert.Equal(t, "12345", routingCtx.TenantID)
		assert.Equal(t, "f02", routingCtx.ProjectID)
		assert.Equal(t, "devtier-infra", routingCtx.ApplicationID)
	})

	t.Run("build routing context without projectID and applicationID defaults to empty", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType): "dev-devtier",
				string(constants.OpenAPIHeaderOrgId):   "12345",
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, "", routingCtx.ProjectID)
		assert.Equal(t, "", routingCtx.ApplicationID)
		// BuildBizRouteKey should fill wildcards for missing segments
		assert.Equal(t, "dev-devtier:12345:.*:.*", routingCtx.BuildBizRouteKey())
	})

	t.Run("build routing context with projectID and applicationID from path params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
			PathParams: []gin.Param{
				{Key: "bizType", Value: "dev-devtier"},
				{Key: "tenantID", Value: "12345"},
				{Key: "projectID", Value: "f02"},
				{Key: "applicationID", Value: "devtier-infra"},
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, "f02", routingCtx.ProjectID)
		assert.Equal(t, "devtier-infra", routingCtx.ApplicationID)
	})

	t.Run("build routing context headers take precedence over path params for projectID and applicationID", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderProjectID):     "header-f03",
				string(constants.OpenAPIHeaderApplicationID): "header-app",
			},
			PathParams: []gin.Param{
				{Key: "projectID", Value: "path-f02"},
				{Key: "applicationID", Value: "path-app"},
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.Equal(t, "header-f03", routingCtx.ProjectID)     // Header takes precedence
		assert.Equal(t, "header-app", routingCtx.ApplicationID) // Header takes precedence
	})

	t.Run("build routing context extracts projectID and applicationID from body for cluster creation", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType): "staging-devtier",
				string(constants.OpenAPIHeaderOrgId):   "12345",
			},
			BodyParams: `{"bizType":"staging-devtier","tenantID":"12345","projectID":"us-east-1-f01","applicationID":"cluster-new-123"}`,
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters/")

		assert.Equal(t, "staging-devtier", routingCtx.BizType)
		assert.Equal(t, "12345", routingCtx.TenantID)
		assert.Equal(t, "us-east-1-f01", routingCtx.ProjectID)
		assert.Equal(t, "cluster-new-123", routingCtx.ApplicationID)
	})

	t.Run("header projectID takes precedence over body", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType):   "staging-devtier",
				string(constants.OpenAPIHeaderOrgId):     "12345",
				string(constants.OpenAPIHeaderProjectID): "us-east-1-f02",
			},
			BodyParams: `{"projectID":"us-east-1-f01"}`,
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters/")

		assert.Equal(t, "us-east-1-f02", routingCtx.ProjectID)
	})

	t.Run("build routing context captures logical region only on whitelisted paths", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-west-2",
			},
			Headers: map[string]string{
				string(constants.OpenAPIHeaderLogicalRegion): "us-west-2-byoc-0303",
			},
		}

		cases := []struct {
			name   string
			path   string
			wantLR string
		}{
			{name: "ops route", path: "/ops/api/v1/helmreleases", wantLR: "us-west-2-byoc-0303"},
			{name: "internal routes", path: "/internal/v1/routes", wantLR: "us-west-2-byoc-0303"},
			{name: "alert rule template", path: "/api/v1/alertrule-template", wantLR: "us-west-2-byoc-0303"},
			{name: "alert rule template subpath", path: "/api/v1/alertrule-template/123", wantLR: "us-west-2-byoc-0303"},
			{name: "non-whitelisted route", path: "/api/v1/routes", wantLR: ""},
		}

		for _, tc := range cases {
			t.Run(tc.name, func(t *testing.T) {
				got := buildRoutingContext(reqParams, tc.path)
				assert.Equal(t, tc.wantLR, got.LogicalRegion)
			})
		}
	})

	t.Run("build routing context carries showDeleted from query params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			CommonReqInfo: structs.CommonReqInfo{
				Vendor: constants.AWS,
				Region: "us-east-1",
			},
			QueryParams: map[string]string{
				"showDeleted": "true",
			},
		}

		routingCtx := buildRoutingContext(reqParams, "/api/v1/clusters")

		assert.True(t, routingCtx.ShowDeleted)
	})
}

func Test_extractParam(t *testing.T) {
	t.Run("extract from headers", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType): "tidbcloud",
				string(constants.OpenAPIHeaderOrgId):   "12345",
			},
		}

		bizType := extractParam(reqParams, paramKeyBizType)
		tenantID := extractParam(reqParams, paramKeyTenantID)

		assert.Equal(t, "tidbcloud", bizType)
		assert.Equal(t, "12345", tenantID)
	})

	t.Run("extract from path params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			PathParams: []gin.Param{
				{Key: "bizType", Value: "serverless"},
				{Key: "tenantID", Value: "67890"},
				{Key: "other", Value: "ignored"},
			},
		}

		bizType := extractParam(reqParams, paramKeyBizType)
		tenantID := extractParam(reqParams, paramKeyTenantID)

		assert.Equal(t, "serverless", bizType)
		assert.Equal(t, "67890", tenantID)
	})

	t.Run("headers take precedence over path params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType): "header-biz",
			},
			PathParams: []gin.Param{
				{Key: "bizType", Value: "path-biz"},
				{Key: "tenantID", Value: "path-tenant"},
			},
		}

		bizType := extractParam(reqParams, paramKeyBizType)
		tenantID := extractParam(reqParams, paramKeyTenantID)

		assert.Equal(t, "header-biz", bizType)   // Header takes precedence
		assert.Equal(t, "path-tenant", tenantID) // Only available in path
	})

	t.Run("extract from query params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			QueryParams: map[string]string{
				"bizType":       "staging-devtier",
				"tenantID":      "67890",
				"projectID":     "us-east-1-f01",
				"applicationID": "cluster-query-123",
			},
		}

		assert.Equal(t, "staging-devtier", extractParam(reqParams, paramKeyBizType))
		assert.Equal(t, "67890", extractParam(reqParams, paramKeyTenantID))
		assert.Equal(t, "us-east-1-f01", extractParam(reqParams, paramKeyProjectID))
		assert.Equal(t, "cluster-query-123", extractParam(reqParams, paramKeyApplicationID))
	})

	t.Run("headers take precedence over query params", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			Headers: map[string]string{
				string(constants.OpenAPIHeaderBizType): "header-biz",
			},
			QueryParams: map[string]string{
				"bizType": "query-biz",
			},
		}

		assert.Equal(t, "header-biz", extractParam(reqParams, paramKeyBizType))
	})

	t.Run("return empty string when param not found in any source", func(t *testing.T) {
		reqParams := structs.RequestParameters{
			Headers: map[string]string{
				"other-header": "value",
			},
			PathParams: []gin.Param{
				{Key: "other-param", Value: "value"},
			},
		}

		bizType := extractParam(reqParams, paramKeyBizType)
		tenantID := extractParam(reqParams, paramKeyTenantID)

		assert.Equal(t, "", bizType)
		assert.Equal(t, "", tenantID)
	})
}

func Test_AutoRouteRegionalServer(t *testing.T) {
	t.Run("handler error", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
		AutoRouteRegionalServer(c, OptionalParam{}, func(c *gin.Context) error {
			return fmt.Errorf("handler error")
		})
		assert.NotEqual(t, http.StatusOK, w.Code)
	})

	t.Run("resolve and req regional server error", func(t *testing.T) {
		gin.SetMode(gin.TestMode)
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
		AutoRouteRegionalServer(c, OptionalParam{})
		assert.NotEqual(t, http.StatusOK, w.Code)
		assert.NotEmpty(t, w.Body.String())
	})
}

func TestBuildRoutingContext_LogicalRegion(t *testing.T) {
	cases := []struct {
		name      string
		path      string
		headers   map[string]string
		wantLR    string
		wantBiz   string
		wantTenID string
	}{
		{
			name: "logical region only",
			path: "/ops/api/v1/helmreleases",
			headers: map[string]string{
				"X-Logical-Region": "aws-us-west-2-a",
			},
			wantLR: "aws-us-west-2-a",
		},
		{
			name: "triple only",
			path: "/api/v1/clusters",
			headers: map[string]string{
				string(constants.OpenAPIHeaderRegionID): "aws-us-west-2",
				string(constants.OpenAPIHeaderOrgId):    "org1",
				string(constants.OpenAPIHeaderBizType):  "byoc",
			},
			wantBiz:   "byoc",
			wantTenID: "org1",
		},
		{
			name: "both present, logical region wins",
			path: "/api/v1/alertrule-template",
			headers: map[string]string{
				"X-Logical-Region":                      "aws-us-west-2-a",
				string(constants.OpenAPIHeaderRegionID): "aws-us-west-2",
				string(constants.OpenAPIHeaderOrgId):    "org1",
				string(constants.OpenAPIHeaderBizType):  "byoc",
			},
			wantLR:    "aws-us-west-2-a",
			wantBiz:   "byoc",
			wantTenID: "org1",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			reqParams := structs.RequestParameters{
				Headers: tc.headers,
			}
			got := buildRoutingContext(reqParams, tc.path)
			assert.Equal(t, tc.wantLR, got.LogicalRegion)
			assert.Equal(t, tc.wantBiz, got.BizType)
			assert.Equal(t, tc.wantTenID, got.TenantID)
		})
	}
}
