package region

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.uber.org/mock/gomock"

	"github.com/tidbcloud/devops/api"
	regionapi "github.com/tidbcloud/devops/api/region"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	testutil "github.com/tidbcloud/devops/pkg/infra/utils/test"
)

func TestRegionController(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Region Controller Suite", testutil.ReporterConfig())
}

var _ = Describe("RegisterRegionProviderIdentity", func() {
	var (
		router       *gin.Engine
		regionClient *coresvccli.MockRegionServiceClient
	)

	BeforeEach(func() {
		gin.SetMode(gin.TestMode)
		router = gin.New()
		regionClient = coresvccli.NewMockRegionServiceClient(gomock.NewController(GinkgoT()))
		controller := &RegionController{
			coresvc: &coresvccli.CoreSvcCli{
				Mgmt: &coresvccli.MgmtService{Region: regionClient},
			},
		}
		Expect(controller.RegisterAPI(&ctrl.Suite{
			Router:         router.Group("/api/v1/devops"),
			InternalRouter: router.Group("/api/v1/devops-internal"),
			ExternalRouter: router.Group("/api/v1/devops-external"),
		})).To(Succeed())
	})

	It("registers a nextgen region provider identity", func() {
		regionClient.EXPECT().RegisterRegionProviderIdentity(gomock.Any(), &mgmtv1.RegisterRegionProviderIdentityRequest{
			DeployType:        mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN,
			RegionLogicalName: "aws-us-west-2",
			RegionProviderIdentity: &nextgenv1.RegionProviderIdentity{
				Identity:  "123456789012",
				IsDefault: true,
			},
		}).Return(&nextgenv1.RegionProviderIdentity{
			Id:        1001,
			Identity:  "123456789012",
			IsDefault: true,
		}, nil)

		body := []byte(`{"region_logical_name":"aws-us-west-2","identity":"123456789012","is_default":true}`)
		recorder := performRequest(router, http.MethodPost,
			"/api/v1/devops/region/regions/provider-identities", body)

		Expect(recorder.Code).To(Equal(http.StatusOK))
		resp := &regionapi.RegisterRegionProviderIdentityResponse{}
		Expect(json.Unmarshal(recorder.Body.Bytes(), resp)).To(Succeed())
		Expect(resp.ErrCode).To(Equal(api.ErrCodeOK))
		Expect(resp.RegionProviderIdentity).To(Equal(&regionapi.RegionProviderIdentity{
			Id:        1001,
			Identity:  "123456789012",
			IsDefault: true,
		}))
	})

	DescribeTable("validates required fields",
		func(body []byte) {
			recorder := performRequest(router, http.MethodPost,
				"/api/v1/devops/region/regions/provider-identities", body)

			Expect(recorder.Code).To(Equal(http.StatusBadRequest))
			resp := &api.BaseResp{}
			Expect(json.Unmarshal(recorder.Body.Bytes(), resp)).To(Succeed())
			Expect(resp.ErrCode).To(Equal(api.ErrCodeInvalidArgument))
		},
		Entry("missing region logical name", []byte(`{"identity":"123456789012"}`)),
		Entry("missing identity", []byte(`{"region_logical_name":"aws-us-west-2"}`)),
		Entry("identity is too long", []byte(`{"region_logical_name":"aws-us-west-2","identity":"`+string(bytes.Repeat([]byte("a"), 256))+`"}`)),
	)

	It("returns the upstream error", func() {
		regionClient.EXPECT().RegisterRegionProviderIdentity(gomock.Any(), gomock.Any()).
			Return(nil, errors.New("tms unavailable"))

		body := []byte(`{"region_logical_name":"aws-us-west-2","identity":"123456789012"}`)
		recorder := performRequest(router, http.MethodPost,
			"/api/v1/devops/region/regions/provider-identities", body)

		Expect(recorder.Code).To(Equal(http.StatusInternalServerError))
		resp := &api.BaseResp{}
		Expect(json.Unmarshal(recorder.Body.Bytes(), resp)).To(Succeed())
		Expect(resp.ErrCode).To(Equal(api.ErrCodeInternal))
	})

	It("does not expose the endpoint on the unauthenticated external route", func() {
		body := []byte(`{"region_logical_name":"aws-us-west-2","identity":"123456789012"}`)
		recorder := performRequest(router, http.MethodPost,
			"/api/v1/devops-external/region/regions/provider-identities", body)

		Expect(recorder.Code).To(Equal(http.StatusNotFound))
	})
})

func performRequest(router http.Handler, method, target string, body []byte) *httptest.ResponseRecorder {
	recorder := httptest.NewRecorder()
	req := httptest.NewRequest(method, target, bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, req)
	return recorder
}
