package regionalserver

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	"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/nextgen-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func TestCheckKms(t *testing.T) {
	t.Run("SuccessAWS", func(t *testing.T) {
		var gotBody []byte
		var gotMethod, gotPath string
		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.WriteHeader(http.StatusOK)
		}))
		defer srv.Close()

		cli := newTestRegionalCli()
		region := newActiveRegion(srv.URL)

		bErr := cli.CheckKms(context.Background(), region, &regionalserver.CheckKmsRequest{
			SharedPoolId: "sp-12345",
			Provider:     "aws",
			KeyId:        "arn:aws:kms:us-west-2:123:key/abc",
			Region:       "us-west-2",
			EncryptionContext: map[string]string{
				"ExternalId": "60205",
			},
		})
		require.Nil(t, bErr)

		assert.Equal(t, http.MethodPost, gotMethod)
		assert.Equal(t, "/dataplane/proxy/sharedPools/sp-12345/check", gotPath)

		var envelope struct {
			TargetType string                          `json:"targetType"`
			Kms        *regionalserver.CheckKmsRequest `json:"kms"`
		}
		require.NoError(t, json.Unmarshal(gotBody, &envelope))
		var rawEnvelope map[string]any
		require.NoError(t, json.Unmarshal(gotBody, &rawEnvelope))
		kmsRaw, ok := rawEnvelope["kms"].(map[string]any)
		require.True(t, ok)
		assert.Equal(t, "kms", envelope.TargetType)
		require.NotNil(t, envelope.Kms)
		assert.Equal(t, "aws", envelope.Kms.Provider)
		assert.Equal(t, "arn:aws:kms:us-west-2:123:key/abc", envelope.Kms.KeyId)
		assert.Equal(t, "us-west-2", envelope.Kms.Region)
		assert.Equal(t, map[string]string{"ExternalId": "60205"}, envelope.Kms.EncryptionContext)
		assert.NotContains(t, kmsRaw, "aws", "request should omit deprecated aws auth block")
		assert.NotContains(t, kmsRaw, "alicloud", "request should omit deprecated alicloud auth block")
	})

	t.Run("SuccessAliCloud", func(t *testing.T) {
		var gotBody []byte
		srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			gotBody, _ = io.ReadAll(r.Body)
			w.WriteHeader(http.StatusOK)
		}))
		defer srv.Close()

		cli := newTestRegionalCli()
		region := newActiveRegion(srv.URL)

		bErr := cli.CheckKms(context.Background(), region, &regionalserver.CheckKmsRequest{
			SharedPoolId: "sp-12345",
			Provider:     "alicloud",
			KeyId:        "key-abc",
			Region:       "cn-hangzhou",
		})
		require.Nil(t, bErr)

		var envelope struct {
			TargetType string                          `json:"targetType"`
			Kms        *regionalserver.CheckKmsRequest `json:"kms"`
		}
		require.NoError(t, json.Unmarshal(gotBody, &envelope))
		var rawEnvelope map[string]any
		require.NoError(t, json.Unmarshal(gotBody, &rawEnvelope))
		kmsRaw, ok := rawEnvelope["kms"].(map[string]any)
		require.True(t, ok)
		require.NotNil(t, envelope.Kms)
		assert.Equal(t, "alicloud", envelope.Kms.Provider)
		assert.Empty(t, envelope.Kms.EncryptionContext)
		assert.NotContains(t, kmsRaw, "aws", "request should omit deprecated aws auth block")
		assert.NotContains(t, kmsRaw, "alicloud", "request should omit deprecated alicloud auth block")
	})

	t.Run("BadRequestPreservesRegionalMessageAndStatusClass", func(t *testing.T) {
		srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusBadRequest)
			_, _ = w.Write([]byte(`{"error":"failed to encrypt with key"}`))
		}))
		defer srv.Close()

		cli := newTestRegionalCli()
		region := newActiveRegion(srv.URL)

		bErr := cli.CheckKms(context.Background(), region, &regionalserver.CheckKmsRequest{
			SharedPoolId: "sp-12345",
			Provider:     "aws",
			KeyId:        "k",
			Region:       "us-west-2",
		})
		require.NotNil(t, bErr)
		assert.Equal(t, bizerr.ErrCodeParams, bErr.ErrCode())
		assert.Equal(t, "failed to encrypt with key", bErr.ErrMsg())
		assert.Contains(t, bErr.Error(), `call region service failed: {"error":"failed to encrypt with key"}`)
	})

	t.Run("MissingSharedPoolId", func(t *testing.T) {
		cli := newTestRegionalCli()
		region := newActiveRegion("http://127.0.0.1")

		bErr := cli.CheckKms(context.Background(), region, &regionalserver.CheckKmsRequest{
			Provider: "aws",
			KeyId:    "k",
			Region:   "us-west-2",
		})
		require.NotNil(t, bErr)
		assert.Contains(t, bErr.Error(), "shared pool id is required")
	})
}

// newTestRegionalCli builds a real regionalServerCliImpl with aggressive
// timeouts so tests fail fast on misrouted requests.
func newTestRegionalCli() regionalserver.Client {
	return NewRegionalServerCli(&config.Config{
		RegionalServerConfig: config.RegionalServerConfig{
			RequestTimeoutInSec:   5,
			RetryWaitTimeoutInSec: 1,
			RetryCount:            0,
		},
	})
}

func newActiveRegion(serviceAddress string) *entity.Region {
	r := &entity.Region{
		Region: &globalcommonv1.Region{
			State:          globalcommonv1.Region_ACTIVE,
			ServiceAddress: serviceAddress,
		},
	}
	return r
}
