package mgmthttpcli

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

	"github.com/go-resty/resty/v2"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	clusterng "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
)

var _ = Describe("NextGenRegionServer", func() {
	var server *httptest.Server

	newProtoNextGenRegionServerClient := func(serverURL, logicRegion, cloudProvider string) NextGenRegionServer {
		restyClient := resty.New().
			SetBaseURL(serverURL).
			SetJSONMarshaler(func(obj any) ([]byte, error) {
				if objP, ok := obj.(proto.Message); ok {
					return protojson.Marshal(objP)
				}
				return json.Marshal(obj)
			})
		return &ngRegionServer{core: restyClient, logicRegion: logicRegion, cloudProvider: cloudProvider}
	}

	AfterEach(func() {
		if server != nil {
			server.Close()
		}
	})

	Describe("CreatePlacementPolicy", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			receivedBody   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()

				payload, err := io.ReadAll(r.Body)
				Expect(err).NotTo(HaveOccurred())

				receivedMethod = r.Method
				receivedPath = r.URL.Path
				receivedBody = string(payload)
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				_, err = w.Write([]byte(`{
					"sharedPoolId":"2002",
					"placementPolicyId":"1001",
					"clusterId":"1001",
					"tikvGroupId":"tikv-dedicated",
					"phase":"AVAILABLE"
				}`))
				Expect(err).NotTo(HaveOccurred())
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("posts through the cluster-service-ng shared pool regional proxy path", func() {
			resp, err := regionClient.CreatePlacementPolicy(context.Background(), &clusterng.CreatePlacementPolicyRequest{
				SharedPoolId: "2002",
				ClusterId:    "1001",
				TikvGroupId:  "tikv-dedicated",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodPost))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/2002/placementPolicies"))
			Expect(receivedBody).To(MatchJSON(`{
				"clusterId": "1001",
				"tikvGroupId": "tikv-dedicated"
			}`))
			Expect(resp.GetPlacementPolicyId()).To(Equal("1001"))
			Expect(resp.GetTikvGroupId()).To(Equal("tikv-dedicated"))
			Expect(resp.GetPhase()).To(Equal(clusterng.PlacementPolicy_AVAILABLE))
		})
	})

	Describe("DeleteTiKVGroup", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			receivedBody   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()

				payload, err := io.ReadAll(r.Body)
				Expect(err).NotTo(HaveOccurred())

				receivedMethod = r.Method
				receivedPath = r.URL.Path
				receivedBody = string(payload)
				w.WriteHeader(http.StatusOK)
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("deletes through the cluster-service-ng shared pool regional proxy path", func() {
			err := regionClient.DeleteTiKVGroup(context.Background(), &DeleteTiKVGroupRequest{
				SharedPoolId: "2002",
				TikvGroupId:  "tikv-dedicated",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodDelete))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/2002/tikvGroups/tikv-dedicated"))
			Expect(receivedBody).To(BeEmpty())
		})
	})

	Describe("ListPlacementPolicies", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedMethod = r.Method
				receivedPath = r.URL.Path
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				_, err := w.Write([]byte(`{
					"placementPolicies": [
						{
							"sharedPoolId":"2002",
							"placementPolicyId":"1001",
							"clusterId":"1001",
							"tikvGroupId":"tikv-dedicated",
							"phase":"AVAILABLE"
						}
					]
				}`))
				Expect(err).NotTo(HaveOccurred())
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("lists through the cluster-service-ng shared pool regional proxy path", func() {
			resp, err := regionClient.ListPlacementPolicies(context.Background(), &clusterng.ListPlacementPoliciesRequest{
				SharedPoolId: "2002",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodGet))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/2002/placementPolicies"))
			Expect(resp.GetPlacementPolicies()).To(HaveLen(1))
			Expect(resp.GetPlacementPolicies()[0].GetPlacementPolicyId()).To(Equal("1001"))
			Expect(resp.GetPlacementPolicies()[0].GetClusterId()).To(Equal("1001"))
			Expect(resp.GetPlacementPolicies()[0].GetTikvGroupId()).To(Equal("tikv-dedicated"))
		})
	})

	Describe("GetPlacementPolicy", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedMethod = r.Method
				receivedPath = r.URL.Path
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				_, err := w.Write([]byte(`{
					"sharedPoolId":"2002",
					"placementPolicyId":"1001",
					"clusterId":"1001",
					"tikvGroupId":"tikv-dedicated",
					"phase":"AVAILABLE"
				}`))
				Expect(err).NotTo(HaveOccurred())
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("gets through the cluster-service-ng shared pool regional proxy path", func() {
			resp, err := regionClient.GetPlacementPolicy(context.Background(), &clusterng.GetPlacementPolicyRequest{
				SharedPoolId:      "2002",
				PlacementPolicyId: "1001",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodGet))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/2002/placementPolicies/1001"))
			Expect(resp.GetPlacementPolicyId()).To(Equal("1001"))
			Expect(resp.GetClusterId()).To(Equal("1001"))
			Expect(resp.GetTikvGroupId()).To(Equal("tikv-dedicated"))
		})
	})

	Describe("UpdatePlacementPolicy", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			receivedBody   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()

				payload, err := io.ReadAll(r.Body)
				Expect(err).NotTo(HaveOccurred())

				receivedMethod = r.Method
				receivedPath = r.URL.Path
				receivedBody = string(payload)
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				_, err = w.Write([]byte(`{
					"sharedPoolId":"2002",
					"placementPolicyId":"3003",
					"clusterId":"1001",
					"tikvGroupId":"tikvprimary",
					"phase":"MODIFYING"
				}`))
				Expect(err).NotTo(HaveOccurred())
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("puts through the cluster-service-ng shared pool regional proxy path", func() {
			resp, err := regionClient.UpdatePlacementPolicy(context.Background(), &clusterng.UpdatePlacementPolicyRequest{
				SharedPoolId:      "2002",
				PlacementPolicyId: "3003",
				TikvGroupId:       "tikvprimary",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodPut))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/2002/placementPolicies/3003"))
			Expect(receivedBody).To(MatchJSON(`{
				"tikvGroupId": "tikvprimary"
			}`))
			Expect(resp.GetPlacementPolicyId()).To(Equal("3003"))
			Expect(resp.GetTikvGroupId()).To(Equal("tikvprimary"))
			Expect(resp.GetPhase()).To(Equal(clusterng.PlacementPolicy_MODIFYING))
		})
	})

	Describe("DeletePlacementPolicy", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			receivedBody   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()

				payload, err := io.ReadAll(r.Body)
				Expect(err).NotTo(HaveOccurred())

				receivedMethod = r.Method
				receivedPath = r.URL.Path
				receivedBody = string(payload)
				w.WriteHeader(http.StatusOK)
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("deletes through the cluster-service-ng shared pool regional proxy path", func() {
			err := regionClient.DeletePlacementPolicy(context.Background(), &clusterng.DeletePlacementPolicyRequest{
				SharedPoolId:      "2002",
				PlacementPolicyId: "3003",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodDelete))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/2002/placementPolicies/3003"))
			Expect(receivedBody).To(BeEmpty())
		})
	})

	Describe("ApplySemConfig", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			receivedBody   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()

				payload, err := io.ReadAll(r.Body)
				Expect(err).NotTo(HaveOccurred())

				receivedMethod = r.Method
				receivedPath = r.URL.Path
				receivedBody = string(payload)
				w.WriteHeader(http.StatusOK)
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("applies sem config through the nextgen regional proxy path", func() {
			err := regionClient.ApplySemConfig(context.Background(), &clusterng.ApplySemConfigRequest{
				SharedPoolId: "123",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodPost))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/123:applySemConfig"))
			Expect(receivedBody).To(ContainSubstring(`"sharedPoolId":"123"`))
		})
	})

	Describe("SetSharedPoolPDDeploymentMode", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			receivedBody   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()
				payload, err := io.ReadAll(r.Body)
				Expect(err).NotTo(HaveOccurred())

				receivedMethod = r.Method
				receivedPath = r.URL.Path
				receivedBody = string(payload)
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				_, err = w.Write([]byte(`{
					"id":"123",
					"phase":"MODIFYING",
					"capacityMode":"RCU_DRIVEN",
					"pdDeploymentMode":"PD_DEPLOYMENT_MODE_MONOLITHIC",
					"pdDeploymentStatus":{
						"currentMode":"PD_DEPLOYMENT_MODE_MICROSERVICE",
						"phase":"PD_DEPLOYMENT_STATUS_PHASE_SWITCHING"
					}
				}`))
				Expect(err).NotTo(HaveOccurred())
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("posts the protobuf request through the premium regional proxy path", func() {
			resp, err := regionClient.SetSharedPoolPDDeploymentMode(context.Background(), &clusterng.SetSharedPoolPDDeploymentModeRequest{
				SharedPoolId:     "123",
				PdDeploymentMode: clusterng.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodPost))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/123:setPDDeploymentMode"))
			Expect(receivedBody).To(ContainSubstring(`"sharedPoolId":"123"`))
			Expect(receivedBody).To(ContainSubstring(`"pdDeploymentMode":"PD_DEPLOYMENT_MODE_MONOLITHIC"`))
			Expect(resp.GetPhase()).To(Equal(clusterng.SharedPool_MODIFYING))
			Expect(resp.GetPdDeploymentStatus().GetPhase()).To(Equal(clusterng.PDDeploymentStatusPhase_PD_DEPLOYMENT_STATUS_PHASE_SWITCHING))
		})

		It("decodes PD deployment fields when reading a shared pool", func() {
			resp, err := regionClient.GetSharedPool(context.Background(), "123")

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodGet))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/123"))
			Expect(resp.CapacityMode).To(Equal(clusterng.CapacityMode_RCU_DRIVEN.String()))
			Expect(resp.PdDeploymentMode).To(Equal(clusterng.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC.String()))
			Expect(resp.PdDeploymentStatus.CurrentMode).To(Equal(clusterng.PDDeploymentMode_PD_DEPLOYMENT_MODE_MICROSERVICE.String()))
			Expect(resp.PdDeploymentStatus.Phase).To(Equal(clusterng.PDDeploymentStatusPhase_PD_DEPLOYMENT_STATUS_PHASE_SWITCHING.String()))
		})
	})

	Describe("SetSharedPoolTiKVRemoteWorkers", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			receivedBody   string
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()
				payload, err := io.ReadAll(r.Body)
				Expect(err).NotTo(HaveOccurred())

				receivedMethod = r.Method
				receivedPath = r.URL.Path
				receivedBody = string(payload)
				if r.URL.Path == "/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/fail:setTiKVRemoteWorkers" {
					http.Error(w, "regional error", http.StatusInternalServerError)
					return
				}
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				_, err = w.Write([]byte(`{
					"id":"123",
					"phase":"AVAILABLE",
					"enabledTikvRemoteWorkers":["COPROCESSOR_WORKER","TIKV_WORKER"],
					"componentGroups":[
						{"componentType":"TIKV","phase":"AVAILABLE"},
						{"componentType":"COPROCESSOR_WORKER","phase":"AVAILABLE"}
					]
				}`))
				Expect(err).NotTo(HaveOccurred())
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "aws-us-1", "aws")
		})

		It("posts the exact desired worker set through the premium regional proxy path", func() {
			resp, err := regionClient.SetSharedPoolTiKVRemoteWorkers(context.Background(), &clusterng.SetSharedPoolTiKVRemoteWorkersRequest{
				SharedPoolId: "123",
				EnabledTikvRemoteWorkers: []clusterng.ComponentType{
					clusterng.ComponentType_TIKV_WORKER,
					clusterng.ComponentType_COPROCESSOR_WORKER,
				},
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(receivedMethod).To(Equal(http.MethodPost))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/aws-us-1/cloudProviders/aws/api/premium/v1/sharedPools/123:setTiKVRemoteWorkers"))
			Expect(receivedBody).To(MatchJSON(`{
				"sharedPoolId": "123",
				"enabledTikvRemoteWorkers": ["TIKV_WORKER", "COPROCESSOR_WORKER"]
			}`))
			Expect(resp.GetPhase()).To(Equal(clusterng.SharedPool_AVAILABLE))
			Expect(resp.GetEnabledTikvRemoteWorkers()).To(Equal([]clusterng.ComponentType{
				clusterng.ComponentType_COPROCESSOR_WORKER,
				clusterng.ComponentType_TIKV_WORKER,
			}))
		})

		It("returns regional request errors", func() {
			_, err := regionClient.SetSharedPoolTiKVRemoteWorkers(context.Background(), &clusterng.SetSharedPoolTiKVRemoteWorkersRequest{
				SharedPoolId: "fail",
				EnabledTikvRemoteWorkers: []clusterng.ComponentType{
					clusterng.ComponentType_TIKV_WORKER,
				},
			})

			Expect(err).To(HaveOccurred())
		})
	})

	Describe("CassandraProxy", func() {
		var (
			regionClient   NextGenRegionServer
			receivedMethod string
			receivedPath   string
			clusterID      = "1234567890"
		)

		BeforeEach(func() {
			server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				receivedMethod = r.Method
				receivedPath = r.URL.Path
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				w.Write([]byte(`{"hostId":"host-1","internalEndpoint":"proxy.svc:9042","phase":"AVAILABLE"}`))
			}))

			regionClient = newProtoNextGenRegionServerClient(server.URL, "us-east-1-ng", "aws")
		})

		It("should send POST request to regional set cassandra proxy path", func() {
			resp, err := regionClient.SetCassandraProxy(context.Background(), clusterID, &clusterng.SetCassandraProxyRequest{
				NodeQuota: &clusterng.NodeQuota{
					NodeSpec:     &clusterng.NodeSpec{Cpu: 4, MemoryGi: 8},
					NodeCount:    2,
					MinNodeCount: 2,
					MaxNodeCount: 6,
				},
			})

			Expect(err).To(BeNil())
			Expect(resp.GetHostId()).To(Equal("host-1"))
			Expect(resp.GetPhase()).To(Equal(clusterng.Component_AVAILABLE))
			Expect(receivedMethod).To(Equal("POST"))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/us-east-1-ng/cloudProviders/aws/cluster-ng-server/api/v1beta/clusters/1234567890:setCassandraProxy"))
		})

		It("should send GET request to regional cassandra proxy path", func() {
			resp, err := regionClient.GetCassandraProxy(context.Background(), clusterID)

			Expect(err).To(BeNil())
			Expect(resp.GetInternalEndpoint()).To(Equal("proxy.svc:9042"))
			Expect(resp.GetPhase()).To(Equal(clusterng.Component_AVAILABLE))
			Expect(receivedMethod).To(Equal("GET"))
			Expect(receivedPath).To(Equal("/proxy/nextgen/regions/us-east-1-ng/cloudProviders/aws/cluster-ng-server/api/v1beta/clusters/1234567890/cassandraProxy"))
		})
	})

})
