package regionalserver

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func TestPauseResumeSharedPoolClient(t *testing.T) {
	tests := []struct {
		name       string
		method     string
		request    any
		wantMethod string
		wantPath   string
		response   string
		assertBody func(t *testing.T, body []byte)
	}{
		{
			name:       "validate",
			method:     "ValidateSharedPoolPause",
			request:    &clusterngv1beta.ValidateSharedPoolPauseRequest{SharedPoolId: "123"},
			wantMethod: http.MethodPost,
			wantPath:   "/api/premium/v1/sharedPools/123:validatePause",
			response:   `{"failures":[{"code":"INVALID_STATE","message":"not active"}]}`,
			assertBody: func(t *testing.T, body []byte) {
				t.Helper()
				assert.Empty(t, body)
			},
		},
		{
			name:       "pause",
			method:     "PauseSharedPool",
			request:    &clusterngv1beta.PauseSharedPoolRequest{SharedPoolId: "123", OperationId: "pause-1"},
			wantMethod: http.MethodPost,
			wantPath:   "/api/premium/v1/sharedPools/123:pause",
			response:   `{"id":"123"}`,
			assertBody: func(t *testing.T, body []byte) {
				t.Helper()
				var got map[string]string
				require.NoError(t, json.Unmarshal(body, &got))
				assert.Equal(t, map[string]string{"operationId": "pause-1"}, got)
			},
		},
		{
			name:       "resume",
			method:     "ResumeSharedPool",
			request:    &clusterngv1beta.ResumeSharedPoolRequest{SharedPoolId: "123", OperationId: "resume-1"},
			wantMethod: http.MethodPost,
			wantPath:   "/api/premium/v1/sharedPools/123:resume",
			response:   `{"id":"123"}`,
			assertBody: func(t *testing.T, body []byte) {
				t.Helper()
				var got map[string]string
				require.NoError(t, json.Unmarshal(body, &got))
				assert.Equal(t, map[string]string{"operationId": "resume-1"}, got)
			},
		},
		{
			name:       "get operation",
			method:     "GetPauseResumeOperation",
			request:    &clusterngv1beta.GetPauseResumeOperationRequest{SharedPoolId: "123", OperationId: "pause-1"},
			wantMethod: http.MethodGet,
			wantPath:   "/api/premium/v1/sharedPools/123/pauseResumeOperations/pause-1",
			response:   `{"sharedPoolId":"123","operationId":"pause-1","status":"SUCCEEDED"}`,
			assertBody: func(t *testing.T, body []byte) {
				t.Helper()
				assert.Empty(t, body)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var gotMethod, gotPath string
			var gotBody []byte
			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				gotMethod = r.Method
				gotPath = r.URL.Path
				gotBody, _ = io.ReadAll(r.Body)
				w.Header().Set("Content-Type", "application/json")
				_, _ = w.Write([]byte(tt.response))
			}))
			defer srv.Close()

			cli := newTestRegionalCli()
			region := newActiveRegion(srv.URL)
			var err error
			switch tt.method {
			case "ValidateSharedPoolPause":
				_, err = cli.ValidateSharedPoolPause(context.Background(), region, tt.request.(*clusterngv1beta.ValidateSharedPoolPauseRequest))
			case "PauseSharedPool":
				_, err = cli.PauseSharedPool(context.Background(), region, tt.request.(*clusterngv1beta.PauseSharedPoolRequest))
			case "ResumeSharedPool":
				_, err = cli.ResumeSharedPool(context.Background(), region, tt.request.(*clusterngv1beta.ResumeSharedPoolRequest))
			case "GetPauseResumeOperation":
				_, err = cli.GetPauseResumeOperation(context.Background(), region, tt.request.(*clusterngv1beta.GetPauseResumeOperationRequest))
			default:
				t.Fatalf("unknown method %q", tt.method)
			}
			require.NoError(t, err)
			assert.Equal(t, tt.wantMethod, gotMethod)
			assert.Equal(t, tt.wantPath, gotPath)
			tt.assertBody(t, gotBody)
		})
	}
}

func TestPauseResumeSharedPoolClientRejectsConflict(t *testing.T) {
	t.Parallel()

	methods := []struct {
		name string
		call func(t *testing.T, cli regionalserver.Client, region *entity.Region) bizerr.BizError
	}{
		{
			name: "validate",
			call: func(t *testing.T, cli regionalserver.Client, region *entity.Region) bizerr.BizError {
				t.Helper()
				_, err := cli.ValidateSharedPoolPause(
					t.Context(),
					region,
					&clusterngv1beta.ValidateSharedPoolPauseRequest{SharedPoolId: "123"},
				)
				return err
			},
		},
		{
			name: "pause",
			call: func(t *testing.T, cli regionalserver.Client, region *entity.Region) bizerr.BizError {
				t.Helper()
				_, err := cli.PauseSharedPool(
					t.Context(),
					region,
					&clusterngv1beta.PauseSharedPoolRequest{SharedPoolId: "123", OperationId: "pause-1"},
				)
				return err
			},
		},
		{
			name: "resume",
			call: func(t *testing.T, cli regionalserver.Client, region *entity.Region) bizerr.BizError {
				t.Helper()
				_, err := cli.ResumeSharedPool(
					t.Context(),
					region,
					&clusterngv1beta.ResumeSharedPoolRequest{SharedPoolId: "123", OperationId: "resume-1"},
				)
				return err
			},
		},
		{
			name: "get operation",
			call: func(t *testing.T, cli regionalserver.Client, region *entity.Region) bizerr.BizError {
				t.Helper()
				_, err := cli.GetPauseResumeOperation(
					t.Context(),
					region,
					&clusterngv1beta.GetPauseResumeOperationRequest{SharedPoolId: "123", OperationId: "pause-1"},
				)
				return err
			},
		},
	}

	for _, method := range methods {
		t.Run(method.name, func(t *testing.T) {
			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
				w.WriteHeader(http.StatusConflict)
				_, _ = w.Write([]byte(`{"code":9,"message":"operation conflict"}`))
			}))
			defer srv.Close()

			err := method.call(t, newTestRegionalCli(), newActiveRegion(srv.URL))
			require.Error(t, err)
			require.Equal(t, bizerr.ErrCodeRegionalAPI, err.ErrCode())
		})
	}
}

func TestGetPauseResumeOperationNotFound(t *testing.T) {
	t.Parallel()

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusNotFound)
	}))
	defer srv.Close()

	_, err := newTestRegionalCli().GetPauseResumeOperation(
		t.Context(),
		newActiveRegion(srv.URL),
		&clusterngv1beta.GetPauseResumeOperationRequest{SharedPoolId: "123", OperationId: "pause-1"},
	)
	require.Error(t, err)
	assert.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
}
