package globalapi

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/common/structs"
	serverlessglobalv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	serverlessregionalv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	tidbmgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

func TestResolveShowDeletedQueryProjectID(t *testing.T) {
	origMgmtListClustersFunc := mgmtListClustersFunc
	defer func() {
		mgmtListClustersFunc = origMgmtListClustersFunc
	}()

	t.Run("dedicated showDeleted resolves originalProjectId", func(t *testing.T) {
		mgmtListClustersFunc = func(ctx context.Context, env string, clusterID uint64, showDeleted bool) (*tidbmgmt.ListClustersResponse, error) {
			assert.Equal(t, constants.EnvProd, env)
			assert.Equal(t, uint64(101), clusterID)
			assert.True(t, showDeleted)
			return &tidbmgmt.ListClustersResponse{
				Clusters: []*tidbmgmt.Cluster{{
					ClusterId:         101,
					DeployType:        tidbmgmt.DeployType_DEPLOY_TYPE_DEDICATED,
					OriginalProjectId: 778899,
				}},
			}, nil
		}

		query, kubernetesID, err := resolveShowDeletedQueryProjectID(context.Background(), message.QueryClustersReq{
			BizType:       "tidbcloud",
			TenantID:      "123",
			ApplicationID: "101",
			ShowDeleted:   true,
		})
		assert.NoError(t, err)
		assert.Equal(t, "778899", query.ProjectID)
		assert.Empty(t, kubernetesID)
	})

	t.Run("serverless showDeleted resolves originalProjectId", func(t *testing.T) {
		mgmtListClustersFunc = func(ctx context.Context, env string, clusterID uint64, showDeleted bool) (*tidbmgmt.ListClustersResponse, error) {
			assert.Equal(t, constants.EnvStaging, env)
			assert.Equal(t, uint64(202), clusterID)
			assert.True(t, showDeleted)
			return &tidbmgmt.ListClustersResponse{
				Clusters: []*tidbmgmt.Cluster{{
					ClusterId:         202,
					DeployType:        tidbmgmt.DeployType_DEPLOY_TYPE_SERVERLESS,
					OriginalProjectId: 556677,
					ClusterInfo: &tidbmgmt.Cluster_ServerlessCluster{
						ServerlessCluster: &serverlessglobalv1.ServerlessServiceCluster{
							Cluster: &serverlessregionalv1.ServerlessRegionalServiceClusterInfo{
								Kubernetes: "staging-us-east-1-f05",
							},
						},
					},
				}},
			}, nil
		}

		query, kubernetesID, err := resolveShowDeletedQueryProjectID(context.Background(), message.QueryClustersReq{
			BizType:       "staging-devtier",
			TenantID:      "456",
			ApplicationID: "202",
			ShowDeleted:   true,
		})
		assert.NoError(t, err)
		assert.Equal(t, "556677", query.ProjectID)
		assert.Equal(t, "staging-us-east-1-f05", kubernetesID)
	})

	t.Run("nextgen bizType does not resolve project id", func(t *testing.T) {
		called := false
		mgmtListClustersFunc = func(ctx context.Context, env string, clusterID uint64, showDeleted bool) (*tidbmgmt.ListClustersResponse, error) {
			called = true
			return nil, nil
		}

		query, kubernetesID, err := resolveShowDeletedQueryProjectID(context.Background(), message.QueryClustersReq{
			BizType:       "staging-tidbcloud-nextgen",
			TenantID:      "789",
			ApplicationID: "303",
			ShowDeleted:   true,
		})
		assert.NoError(t, err)
		assert.Empty(t, query.ProjectID)
		assert.Empty(t, kubernetesID)
		assert.False(t, called)
	})

	t.Run("existing project id skips lookup", func(t *testing.T) {
		called := false
		mgmtListClustersFunc = func(ctx context.Context, env string, clusterID uint64, showDeleted bool) (*tidbmgmt.ListClustersResponse, error) {
			called = true
			return nil, nil
		}

		query, kubernetesID, err := resolveShowDeletedQueryProjectID(context.Background(), message.QueryClustersReq{
			BizType:       "tidbcloud",
			TenantID:      "123",
			ProjectID:     "already-set",
			ApplicationID: "101",
			ShowDeleted:   true,
		})
		assert.NoError(t, err)
		assert.Equal(t, "already-set", query.ProjectID)
		assert.Empty(t, kubernetesID)
		assert.False(t, called)
	})
}

func TestEnrichShowDeletedQueryClusters(t *testing.T) {
	origMgmtListClustersFunc := mgmtListClustersFunc
	defer func() {
		mgmtListClustersFunc = origMgmtListClustersFunc
	}()

	gin.SetMode(gin.TestMode)

	t.Run("updates raw query only for dedicated showDeleted lookup", func(t *testing.T) {
		mgmtListClustersFunc = func(ctx context.Context, env string, clusterID uint64, showDeleted bool) (*tidbmgmt.ListClustersResponse, error) {
			return &tidbmgmt.ListClustersResponse{
				Clusters: []*tidbmgmt.Cluster{{
					ClusterId:         101,
					DeployType:        tidbmgmt.DeployType_DEPLOY_TYPE_DEDICATED,
					OriginalProjectId: 778899,
				}},
			}, nil
		}

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/clusters?bizType=tidbcloud&tenantID=123&applicationID=101&showDeleted=true", nil)
		c.Set(structs.GlobalServerRequestParametersKey, structs.RequestParameters{
			QueryParams: map[string]string{
				"bizType":       "tidbcloud",
				"tenantID":      "123",
				"applicationID": "101",
				"showDeleted":   "true",
			},
		})

		err := enrichShowDeletedQueryClusters(c)
		assert.NoError(t, err)

		obj, ok := c.Get(structs.GlobalServerRequestParametersKey)
		assert.True(t, ok)
		reqParams := obj.(structs.RequestParameters)
		_, exists := reqParams.QueryParams[paramKeyProjectID]
		assert.False(t, exists)

		parsed, parseErr := url.Parse(c.Request.URL.String())
		assert.NoError(t, parseErr)
		assert.Equal(t, "778899", parsed.Query().Get(paramKeyProjectID))
	})

	t.Run("serverless lookup stores kubernetes header for downstream resolver", func(t *testing.T) {
		mgmtListClustersFunc = func(ctx context.Context, env string, clusterID uint64, showDeleted bool) (*tidbmgmt.ListClustersResponse, error) {
			return &tidbmgmt.ListClustersResponse{
				Clusters: []*tidbmgmt.Cluster{{
					ClusterId:         202,
					DeployType:        tidbmgmt.DeployType_DEPLOY_TYPE_SERVERLESS,
					OriginalProjectId: 556677,
					ClusterInfo: &tidbmgmt.Cluster_ServerlessCluster{
						ServerlessCluster: &serverlessglobalv1.ServerlessServiceCluster{
							Cluster: &serverlessregionalv1.ServerlessRegionalServiceClusterInfo{
								Kubernetes: "staging-us-east-1-f05",
							},
						},
					},
				}},
			}, nil
		}

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/clusters?bizType=staging-devtier&tenantID=456&applicationID=202&showDeleted=true", nil)
		c.Set(structs.GlobalServerRequestParametersKey, structs.RequestParameters{
			QueryParams: map[string]string{
				"bizType":       "staging-devtier",
				"tenantID":      "456",
				"applicationID": "202",
				"showDeleted":   "true",
			},
		})

		err := enrichShowDeletedQueryClusters(c)
		assert.NoError(t, err)

		obj, ok := c.Get(structs.GlobalServerRequestParametersKey)
		assert.True(t, ok)
		reqParams := obj.(structs.RequestParameters)
		assert.Equal(t, "staging-us-east-1-f05", reqParams.Headers[string(constants.OpenAPIHeaderKubernetesID)])
		assert.Equal(t, "staging-us-east-1-f05", c.Request.Header.Get(string(constants.OpenAPIHeaderKubernetesID)))

		parsed, parseErr := url.Parse(c.Request.URL.String())
		assert.NoError(t, parseErr)
		assert.Equal(t, "556677", parsed.Query().Get(paramKeyProjectID))
	})

	t.Run("mgmt failure falls back to original query", func(t *testing.T) {
		mgmtListClustersFunc = func(ctx context.Context, env string, clusterID uint64, showDeleted bool) (*tidbmgmt.ListClustersResponse, error) {
			return nil, fmt.Errorf("mgmt unavailable")
		}

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/clusters?bizType=tidbcloud&tenantID=123&applicationID=101&showDeleted=true", nil)
		c.Set(structs.GlobalServerRequestParametersKey, structs.RequestParameters{
			QueryParams: map[string]string{
				"bizType":       "tidbcloud",
				"tenantID":      "123",
				"applicationID": "101",
				"showDeleted":   "true",
			},
		})

		err := enrichShowDeletedQueryClusters(c)
		assert.NoError(t, err)

		obj, ok := c.Get(structs.GlobalServerRequestParametersKey)
		assert.True(t, ok)
		reqParams := obj.(structs.RequestParameters)
		_, exists := reqParams.QueryParams[paramKeyProjectID]
		assert.False(t, exists)

		parsed, parseErr := url.Parse(c.Request.URL.String())
		assert.NoError(t, parseErr)
		assert.Empty(t, parsed.Query().Get(paramKeyProjectID))
	})
}
