package clustergateway

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
)

type roundTripFunc func(*http.Request) (*http.Response, error)

func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
	return f(request)
}

// newTestSharedPool creates a SharedPool for testing with the given id and gateway URL.
func newTestSharedPool(id, gatewayURL string) *entity.SharedPool {
	return &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:             id,
			GatewayBaseUrl: gatewayURL,
			Provider: &apiv1.Provider{
				AuthToken: "test-token",
			},
		},
	}
}

// newTestClient creates a clusterGatewayImpl with custom timeout for testing.
func newTestClient() *clusterGatewayImpl {
	return NewClusterGatewayImpl().(*clusterGatewayImpl)
}

func TestExoticBackupInfo_UnmarshalBackupTs(t *testing.T) {
	body := `{"backup_ts":464972775884324865,"backup_time":"2026-03-17T06:52:56.756+00:00"}`

	var info ExoticBackupInfo
	err := json.Unmarshal([]byte(body), &info)
	require.NoError(t, err)

	assert.Equal(t, uint64(464972775884324865), info.BackupTs)
}

func TestFlushWALErrorContract(t *testing.T) {
	tests := []struct {
		name       string
		statusCode int
		body       string
		check      func(*testing.T, error)
	}{
		{name: "success", statusCode: http.StatusOK, check: func(t *testing.T, err error) { t.Helper(); require.NoError(t, err) }},
		{name: "unsupported", statusCode: http.StatusNotFound, check: func(t *testing.T, err error) { t.Helper(); require.ErrorIs(t, err, ErrFlushWALUnsupported) }},
		{name: "status only", statusCode: http.StatusBadRequest, body: "secret response body", check: func(t *testing.T, err error) {
			t.Helper()
			var httpErr *FlushWALHTTPError
			require.ErrorAs(t, err, &httpErr)
			require.Equal(t, http.StatusBadRequest, httpErr.StatusCode)
			require.NotContains(t, err.Error(), "secret response body")
		}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			client := newTestClient()
			client.client.SetTransport(roundTripFunc(func(request *http.Request) (*http.Response, error) {
				return &http.Response{
					StatusCode: tt.statusCode,
					Status:     http.StatusText(tt.statusCode),
					Header:     make(http.Header),
					Body:       io.NopCloser(strings.NewReader(tt.body)),
					Request:    request,
				}, nil
			}))
			err := client.FlushWAL(context.Background(), newTestSharedPool("123", "http://gateway.test"), "default")
			tt.check(t, err)
		})
	}
}

func TestGetBackupStatusErrorDoesNotExposeResponseBody(t *testing.T) {
	client := newTestClient()
	client.client.SetTransport(roundTripFunc(func(request *http.Request) (*http.Response, error) {
		return &http.Response{
			StatusCode: http.StatusBadGateway,
			Status:     http.StatusText(http.StatusBadGateway),
			Header:     make(http.Header),
			Body:       io.NopCloser(strings.NewReader("secret response body")),
			Request:    request,
		}, nil
	}))

	_, err := client.GetBackupStatus(context.Background(), newTestSharedPool("123", "http://gateway.test"), "default")
	var httpErr *BackupStatusHTTPError
	require.ErrorAs(t, err, &httpErr)
	require.Equal(t, http.StatusBadGateway, httpErr.StatusCode)
	require.NotContains(t, err.Error(), "secret response body")
}

func TestGetLightweightBackup(t *testing.T) {
	testCases := []struct {
		name           string
		sharedPoolId   string
		tibrCRName     string
		request        *GetLightweightBackupRequest
		mockStatusCode int
		mockResponse   string
		wantErr        bool
		validatePath   func(t *testing.T, path string, sharedPoolId string)
	}{
		{
			name:         "success with sharedPoolId in path",
			sharedPoolId: "123456",
			tibrCRName:   "tibr-test",
			request: &GetLightweightBackupRequest{
				Query:       "for-pitr",
				PointInTime: "2024-01-01T00:00:00Z",
			},
			mockStatusCode: http.StatusOK,
			mockResponse: `{
				"v1_compat_id": 1,
				"name": "backup-1",
				"last_modify_time": "2024-01-01T00:00:00Z",
				"details": {
					"backup_ts": 12345,
					"safe_ts": 12345,
					"keyspaces": []
				}
			}`,
			wantErr: false,
			validatePath: func(t *testing.T, path string, sharedPoolId string) {
				t.Helper()
				assert.Contains(t, path, "/tibr/tibr-test/api/v1/x/backup")
			},
		},
		{
			name:         "failure with non-200 status code",
			sharedPoolId: "789012",
			tibrCRName:   "tibr-test",
			request: &GetLightweightBackupRequest{
				Query: "for-pitr",
			},
			mockStatusCode: http.StatusInternalServerError,
			mockResponse:   `{"error": "internal server error"}`,
			wantErr:        true,
			validatePath:   nil,
		},
		{
			name:         "with optional parameters",
			sharedPoolId: "111222",
			tibrCRName:   "tibr-optional",
			request: &GetLightweightBackupRequest{
				Query:       "for-pitr",
				PointInTime: "2024-01-01T00:00:00Z",
				UseS3Bucket: "test-bucket",
				UsePrefix:   "test-prefix",
				UseEndpoint: "https://account-a.blob.core.windows.net",
			},
			mockStatusCode: http.StatusOK,
			mockResponse: `{
				"v1_compat_id": 2,
				"name": "backup-2",
				"last_modify_time": "2024-01-01T00:00:00Z",
				"details": {}
			}`,
			wantErr: false,
			validatePath: func(t *testing.T, path string, sharedPoolId string) {
				t.Helper()
				assert.Contains(t, path, "/tibr/tibr-optional/api/v1/x/backup")
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var receivedPath string
			var receivedQuery string

			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedPath = r.URL.Path
				receivedQuery = r.URL.RawQuery

				w.WriteHeader(tc.mockStatusCode)
				_, _ = w.Write([]byte(tc.mockResponse))
			}))
			defer server.Close()

			client := newTestClient()
			sharedPool := newTestSharedPool(tc.sharedPoolId, server.URL)

			result, err := client.GetLightweightBackup(context.Background(), sharedPool, tc.tibrCRName, tc.request)

			if tc.wantErr {
				assert.Error(t, err)
				return
			}

			require.NoError(t, err)
			assert.NotNil(t, result)

			if tc.validatePath != nil {
				tc.validatePath(t, receivedPath, tc.sharedPoolId)
			}

			// Verify query parameters
			assert.Contains(t, receivedQuery, "query="+tc.request.Query)
			if tc.request.PointInTime != "" {
				assert.Contains(t, receivedQuery, "point_in_time=")
			}
			if tc.request.UseS3Bucket != "" {
				assert.Contains(t, receivedQuery, "use_s3_bucket="+tc.request.UseS3Bucket)
			}
			if tc.request.UsePrefix != "" {
				assert.Contains(t, receivedQuery, "use_prefix="+tc.request.UsePrefix)
			}
			if tc.request.UseEndpoint != "" {
				assert.Contains(t, receivedQuery, "use_endpoint="+url.QueryEscape(tc.request.UseEndpoint))
			}
		})
	}
}

func TestCreateLightweightRestore(t *testing.T) {
	testCases := []struct {
		name           string
		sharedPoolId   string
		tibrCRName     string
		request        *CreateLightweightRestoreRequest
		mockStatusCode int
		mockResponse   string
		wantErr        bool
		wantConflict   bool
	}{
		{
			name:         "success create restore",
			sharedPoolId: "123456",
			tibrCRName:   "tibr-test",
			request: &CreateLightweightRestoreRequest{
				RestoreId:      "restore-1",
				PDClusterId:    "pd-123",
				KeySpace:       "keyspace-1",
				SourceKeySpace: "source-keyspace",
				BackupId:       "backup-1",
			},
			mockStatusCode: http.StatusCreated,
			mockResponse: `{
				"id": 1,
				"keyspace": "keyspace-1",
				"status": "Pending"
			}`,
			wantErr: false,
		},
		{
			name:         "success with backup name and point in time",
			sharedPoolId: "789012",
			tibrCRName:   "tibr-pitr",
			request: &CreateLightweightRestoreRequest{
				RestoreId:      "restore-2",
				PDClusterId:    "pd-456",
				KeySpace:       "keyspace-2",
				SourceKeySpace: "source-keyspace",
				BackupName:     "backup-name.meta",
				PointInTime:    "2024-01-01T00:00:00Z",
			},
			mockStatusCode: http.StatusAccepted,
			mockResponse: `{
				"id": 2,
				"keyspace": "keyspace-2",
				"restore_type": "Pitr",
				"status": "Pending"
			}`,
			wantErr: false,
		},
		{
			name:         "conflict restore already exists",
			sharedPoolId: "111222",
			tibrCRName:   "tibr-conflict",
			request: &CreateLightweightRestoreRequest{
				RestoreId:      "restore-3",
				PDClusterId:    "pd-789",
				KeySpace:       "keyspace-3",
				SourceKeySpace: "source-keyspace",
			},
			mockStatusCode: http.StatusConflict,
			mockResponse: `{
				"id": 3,
				"keyspace": "keyspace-3",
				"conflict_restore_id": 999
			}`,
			wantErr:      false,
			wantConflict: true,
		},
		{
			name:         "failure with error status",
			sharedPoolId: "333444",
			tibrCRName:   "tibr-error",
			request: &CreateLightweightRestoreRequest{
				RestoreId:      "restore-4",
				PDClusterId:    "pd-000",
				KeySpace:       "keyspace-4",
				SourceKeySpace: "source-keyspace",
			},
			mockStatusCode: http.StatusInternalServerError,
			mockResponse:   `{"error": "internal server error"}`,
			wantErr:        true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var receivedPath string
			var receivedQuery string

			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedPath = r.URL.Path
				receivedQuery = r.URL.RawQuery

				// Verify HTTP method
				assert.Equal(t, http.MethodPut, r.Method)

				w.WriteHeader(tc.mockStatusCode)
				_, _ = w.Write([]byte(tc.mockResponse))
			}))
			defer server.Close()

			client := newTestClient()
			sharedPool := newTestSharedPool(tc.sharedPoolId, server.URL)

			result, err := client.CreateLightweightRestore(context.Background(), sharedPool, tc.tibrCRName, tc.request)

			if tc.wantErr {
				assert.Error(t, err)
				return
			}

			require.NoError(t, err)
			assert.NotNil(t, result)

			// Verify path contains tibrCRName
			assert.Contains(t, receivedPath, "/tibr/"+tc.tibrCRName)
			assert.Contains(t, receivedPath, "/api/v1/restore_keyspace/"+tc.request.RestoreId)

			// Verify query parameters
			assert.Contains(t, receivedQuery, "cluster_id="+tc.request.PDClusterId)
			assert.Contains(t, receivedQuery, "keyspace="+tc.request.KeySpace)
			assert.Contains(t, receivedQuery, "source_keyspace="+tc.request.SourceKeySpace)

			if tc.request.BackupId != "" {
				assert.Contains(t, receivedQuery, "backup_id="+tc.request.BackupId)
			}
			if strings.TrimSpace(tc.request.BackupName) != "" {
				assert.Contains(t, receivedQuery, "backup_name=")
			}
			if strings.TrimSpace(tc.request.PointInTime) != "" {
				assert.Contains(t, receivedQuery, "point_in_time=")
			}

			if tc.wantConflict {
				assert.NotZero(t, result.ConflictRestoreId)
			}
		})
	}
}

func TestCreateCopyPackedBackupTask_IncludesAzureParams(t *testing.T) {
	var receivedPath string
	var receivedQuery url.Values

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedPath = r.URL.Path
		receivedQuery = r.URL.Query()

		assert.Equal(t, http.MethodPut, r.Method)
		w.WriteHeader(http.StatusAccepted)
		_, _ = w.Write([]byte(`{"id":303,"state":"Pending","state_trans":[]}`))
	}))
	defer server.Close()

	client := newTestClient()
	sharedPool := newTestSharedPool("123456", server.URL)

	resp, err := client.CreateCopyPackedBackupTask(context.Background(), sharedPool, "tibr-copy", &CreateCopyPackedBackupTaskRequest{
		TaskId:       "303",
		ExoticBackup: "container-a/prefix/packed.meta",
		CopyTo:       "backup-copy",
		UsePrefix:    "manual-backup/cluster-1",
	})

	require.NoError(t, err)
	require.NotNil(t, resp)
	assert.Equal(t, "/proxy/cluster/123456/tibr/tibr-copy/api/v1/x/task/copy_backup/303", receivedPath)
	assert.Equal(t, "container-a/prefix/packed.meta", receivedQuery.Get("exotic_backup"))
	assert.Equal(t, "backup-copy", receivedQuery.Get("copy_to"))
	assert.Equal(t, "manual-backup/cluster-1", receivedQuery.Get("use_prefix"))
}

func TestCreateRestorePackedBackupTask_IncludesAzureEndpoint(t *testing.T) {
	var receivedPath string
	var receivedQuery url.Values

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedPath = r.URL.Path
		receivedQuery = r.URL.Query()

		assert.Equal(t, http.MethodPut, r.Method)
		w.WriteHeader(http.StatusAccepted)
		_, _ = w.Write([]byte(`{"id":202,"state":"Pending","state_trans":[]}`))
	}))
	defer server.Close()

	client := newTestClient()
	sharedPool := newTestSharedPool("123456", server.URL)

	resp, err := client.CreateRestorePackedBackupTask(context.Background(), sharedPool, "tibr-restore", &CreateRestorePackedBackupTaskRequest{
		TaskId:               "202",
		ClusterId:            "789",
		ExoticBackup:         "container-a/prefix/packed.meta",
		Keyspace:             "ks1",
		PointInTime:          "2024-01-01T00:00:00Z",
		ExoticBackupEndpoint: "https://account-a.blob.core.windows.net",
	})

	require.NoError(t, err)
	require.NotNil(t, resp)
	assert.Equal(t, "/proxy/cluster/123456/tibr/tibr-restore/api/v1/x/task/restore_packed/202", receivedPath)
	assert.Equal(t, "789", receivedQuery.Get("cluster_id"))
	assert.Equal(t, "container-a/prefix/packed.meta", receivedQuery.Get("exotic_backup"))
	assert.Equal(t, "ks1", receivedQuery.Get("keyspace"))
	assert.Equal(t, "2024-01-01T00:00:00Z", receivedQuery.Get("point_in_time"))
	assert.Equal(t, "https://account-a.blob.core.windows.net", receivedQuery.Get("exotic_backup_endpoint"))
}

func TestCreatePackBackupTask_IncludesAzureEndpoint(t *testing.T) {
	var receivedPath string
	var receivedQuery url.Values

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedPath = r.URL.Path
		receivedQuery = r.URL.Query()

		assert.Equal(t, http.MethodPut, r.Method)
		w.WriteHeader(http.StatusAccepted)
		_, _ = w.Write([]byte(`{"id":101,"state":"Pending","state_trans":[]}`))
	}))
	defer server.Close()

	client := newTestClient()
	sharedPool := newTestSharedPool("123456", server.URL)

	resp, err := client.CreatePackBackupTask(context.Background(), sharedPool, "tibr-pack", &CreatePackBackupTaskRequest{
		TaskId:      "101",
		BackupName:  "backup-1",
		Keyspace:    "ks1",
		UseS3Bucket: "container-a",
		UsePrefix:   "prefix-a",
		UseEndpoint: "https://account-a.blob.core.windows.net",
	})

	require.NoError(t, err)
	require.NotNil(t, resp)
	assert.Equal(t, "/proxy/cluster/123456/tibr/tibr-pack/api/v1/x/task/pack_backup/101", receivedPath)
	assert.Equal(t, "backup-1", receivedQuery.Get("backup_name"))
	assert.Equal(t, "ks1", receivedQuery.Get("keyspace"))
	assert.Equal(t, "container-a", receivedQuery.Get("use_s3_bucket"))
	assert.Equal(t, "prefix-a", receivedQuery.Get("use_prefix"))
	assert.Equal(t, "https://account-a.blob.core.windows.net", receivedQuery.Get("use_endpoint"))
}

func TestGetTiBRTask(t *testing.T) {
	testCases := []struct {
		name           string
		sharedPoolId   string
		tibrCRName     string
		taskId         string
		mockStatusCode int
		mockResponse   string
		wantErr        bool
	}{
		{
			name:           "success get task",
			sharedPoolId:   "123456",
			tibrCRName:     "tibr-test",
			taskId:         "task-1",
			mockStatusCode: http.StatusOK,
			mockResponse: `{
				"id": 1,
				"state": "Done",
				"create_time": "2024-01-01T00:00:00Z",
				"last_update_time": "2024-01-01T01:00:00Z"
			}`,
			wantErr: false,
		},
		{
			name:           "task not found returns error",
			sharedPoolId:   "789012",
			tibrCRName:     "tibr-notfound",
			taskId:         "task-notfound",
			mockStatusCode: http.StatusNotFound,
			mockResponse:   `{"error": "not found"}`,
			wantErr:        true,
			// Note: Due to callClusterProxy returning error for non-2xx status,
			// the error is wrapped, not ErrBRTaskNotFound directly
		},
		{
			name:           "internal server error",
			sharedPoolId:   "111222",
			tibrCRName:     "tibr-error",
			taskId:         "task-error",
			mockStatusCode: http.StatusInternalServerError,
			mockResponse:   `{"error": "internal error"}`,
			wantErr:        true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var receivedPath string

			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedPath = r.URL.Path

				// Verify HTTP method
				assert.Equal(t, http.MethodGet, r.Method)

				w.WriteHeader(tc.mockStatusCode)
				_, _ = w.Write([]byte(tc.mockResponse))
			}))
			defer server.Close()

			client := newTestClient()
			sharedPool := newTestSharedPool(tc.sharedPoolId, server.URL)

			result, err := client.GetTiBRTask(context.Background(), sharedPool, tc.tibrCRName, tc.taskId)

			if tc.wantErr {
				assert.Error(t, err)
				return
			}

			require.NoError(t, err)
			assert.NotNil(t, result)

			// Verify path contains tibrCRName and taskId
			assert.Contains(t, receivedPath, "/tibr/"+tc.tibrCRName)
			assert.Contains(t, receivedPath, "/api/v1/x/task/"+tc.taskId)
		})
	}
}

func TestDeleteTiBRTask(t *testing.T) {
	testCases := []struct {
		name           string
		sharedPoolId   string
		tibrCRName     string
		taskId         string
		mockStatusCode int
		mockResponse   string
		wantErr        bool
		wantRunning    bool
	}{
		{
			name:           "success delete task",
			sharedPoolId:   "123456",
			tibrCRName:     "tibr-test",
			taskId:         "task-1",
			mockStatusCode: http.StatusOK,
			mockResponse: `{
				"id": 1,
				"state": "Done"
			}`,
			wantErr: false,
		},
		{
			name:           "task not found returns error",
			sharedPoolId:   "789012",
			tibrCRName:     "tibr-notfound",
			taskId:         "task-notfound",
			mockStatusCode: http.StatusNotFound,
			mockResponse:   `{"error": "not found"}`,
			wantErr:        true,
			// Note: Due to callClusterProxy returning error for non-2xx status,
			// the error is wrapped, not ErrBRTaskNotFound directly
		},
		{
			name:           "task still running - conflict",
			sharedPoolId:   "111222",
			tibrCRName:     "tibr-running",
			taskId:         "task-running",
			mockStatusCode: http.StatusConflict,
			mockResponse:   `{"error": "task is still running"}`,
			wantErr:        true,
			wantRunning:    true,
		},
		{
			name:           "internal server error",
			sharedPoolId:   "333444",
			tibrCRName:     "tibr-error",
			taskId:         "task-error",
			mockStatusCode: http.StatusInternalServerError,
			mockResponse:   `{"error": "internal error"}`,
			wantErr:        true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var receivedPath string

			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedPath = r.URL.Path

				// Verify HTTP method
				assert.Equal(t, http.MethodDelete, r.Method)

				w.WriteHeader(tc.mockStatusCode)
				_, _ = w.Write([]byte(tc.mockResponse))
			}))
			defer server.Close()

			client := newTestClient()
			sharedPool := newTestSharedPool(tc.sharedPoolId, server.URL)

			result, err := client.DeleteTiBRTask(context.Background(), sharedPool, tc.tibrCRName, tc.taskId)

			if tc.wantRunning {
				require.Error(t, err)
				assert.Contains(t, err.Error(), "still running")
				return
			}

			if tc.wantErr {
				assert.Error(t, err)
				return
			}

			require.NoError(t, err)
			assert.NotNil(t, result)

			// Verify path contains tibrCRName and taskId
			assert.Contains(t, receivedPath, "/tibr/"+tc.tibrCRName)
			assert.Contains(t, receivedPath, "/api/v1/x/task/"+tc.taskId)
		})
	}
}

// TestTiBRAPIPathStructure verifies the TiBR API path structure.
// The path format should be: /proxy/cluster/{sharedPoolId}/tibr/{tibrCRName}/*
// This test validates that both sharedPoolId and tibrCRName are correctly substituted.
func TestTiBRAPIPathStructure(t *testing.T) {
	testCases := []struct {
		name         string
		sharedPoolId string
		tibrCRName   string
		operation    string
		expectedPath string // Expected full path pattern
		callFunc     func(client *clusterGatewayImpl, sharedPool *entity.SharedPool) error
	}{
		{
			name:         "GetLightweightBackup path with sharedPoolId and tibrCRName",
			sharedPoolId: "sp123456",
			tibrCRName:   "tibr-backup-cr",
			operation:    "GetLightweightBackup",
			expectedPath: "/proxy/cluster/sp123456/tibr/tibr-backup-cr/api/v1/x/backup",
			callFunc: func(client *clusterGatewayImpl, sharedPool *entity.SharedPool) error {
				_, err := client.GetLightweightBackup(context.Background(), sharedPool, "tibr-backup-cr", &GetLightweightBackupRequest{Query: "test"})
				return err
			},
		},
		{
			name:         "CreateLightweightRestore path with sharedPoolId and tibrCRName",
			sharedPoolId: "sp789012",
			tibrCRName:   "tibr-restore-cr",
			operation:    "CreateLightweightRestore",
			expectedPath: "/proxy/cluster/sp789012/tibr/tibr-restore-cr/api/v1/restore_keyspace/restore-id-1",
			callFunc: func(client *clusterGatewayImpl, sharedPool *entity.SharedPool) error {
				_, err := client.CreateLightweightRestore(context.Background(), sharedPool, "tibr-restore-cr", &CreateLightweightRestoreRequest{
					RestoreId:      "restore-id-1",
					PDClusterId:    "pd1",
					KeySpace:       "ks1",
					SourceKeySpace: "sks1",
				})
				return err
			},
		},
		{
			name:         "GetTiBRTask path with sharedPoolId and tibrCRName",
			sharedPoolId: "sp111222",
			tibrCRName:   "tibr-task-cr",
			operation:    "GetTiBRTask",
			expectedPath: "/proxy/cluster/sp111222/tibr/tibr-task-cr/api/v1/x/task/task-abc",
			callFunc: func(client *clusterGatewayImpl, sharedPool *entity.SharedPool) error {
				_, err := client.GetTiBRTask(context.Background(), sharedPool, "tibr-task-cr", "task-abc")
				return err
			},
		},
		{
			name:         "DeleteTiBRTask path with sharedPoolId and tibrCRName",
			sharedPoolId: "sp333444",
			tibrCRName:   "tibr-delete-cr",
			operation:    "DeleteTiBRTask",
			expectedPath: "/proxy/cluster/sp333444/tibr/tibr-delete-cr/api/v1/x/task/task-xyz",
			callFunc: func(client *clusterGatewayImpl, sharedPool *entity.SharedPool) error {
				_, err := client.DeleteTiBRTask(context.Background(), sharedPool, "tibr-delete-cr", "task-xyz")
				return err
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var receivedPath string

			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedPath = r.URL.Path

				// Return a valid response
				w.WriteHeader(http.StatusOK)
				resp := map[string]any{"id": 1, "state": "Done"}
				_ = json.NewEncoder(w).Encode(resp)
			}))
			defer server.Close()

			client := newTestClient()
			sharedPool := newTestSharedPool(tc.sharedPoolId, server.URL)

			// Call the function
			_ = tc.callFunc(client, sharedPool)

			// Verify the exact path matches expected
			assert.Equal(t, tc.expectedPath, receivedPath,
				"Path should be exactly '%s' for operation %s. Got: %s",
				tc.expectedPath, tc.operation, receivedPath)

			// Also verify the path contains sharedPoolId (not dataplaneNamespace like tidbXXX)
			assert.Contains(t, receivedPath, "/proxy/cluster/"+tc.sharedPoolId+"/",
				"Path should contain sharedPoolId '%s', not dataplaneNamespace", tc.sharedPoolId)
			assert.NotContains(t, receivedPath, "tidb"+tc.sharedPoolId,
				"Path should NOT contain dataplaneNamespace format 'tidb%s'", tc.sharedPoolId)
		})
	}
}

func TestCreateCopyPackedBackupTask_WithMaxThroughput(t *testing.T) {
	var receivedPath string
	var receivedQuery string

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedPath = r.URL.Path
		receivedQuery = r.URL.RawQuery
		w.WriteHeader(http.StatusAccepted)
		_, _ = w.Write([]byte(`{"id":1,"state":"Pending"}`))
	}))
	defer server.Close()

	client := newTestClient()
	sharedPool := newTestSharedPool("sp-copy", server.URL)

	resp, err := client.CreateCopyPackedBackupTask(context.Background(), sharedPool, "tibr-copy", &CreateCopyPackedBackupTaskRequest{
		TaskId:        "copy-task-id",
		ExoticBackup:  "s3://bucket/source.meta",
		CopyTo:        "backup-123",
		UsePrefix:     "manual-backup/cluster-1",
		MaxThroughput: "1250Mib",
	})
	require.NoError(t, err)
	require.NotNil(t, resp)

	assert.Equal(t, "/proxy/cluster/sp-copy/tibr/tibr-copy/api/v1/x/task/copy_backup/copy-task-id", receivedPath)
	assert.Contains(t, receivedQuery, "exotic_backup=s3%3A%2F%2Fbucket%2Fsource.meta")
	assert.Contains(t, receivedQuery, "copy_to=backup-123")
	assert.Contains(t, receivedQuery, "use_prefix=manual-backup%2Fcluster-1")
	assert.Contains(t, receivedQuery, "max_throughput=1250Mib")
}
