package clusteraction

import (
	"context"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	"go.uber.org/mock/gomock"
	v1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/types/known/emptypb"
)

func newMigratePrivateLinkActionWithClient(client mgmtv1.PrivateLinkServiceMigrationServiceClient) *MigrateClusterPrivateLink {
	return &MigrateClusterPrivateLink{
		comps: &actions.Components{
			CoreSvc: &coresvccli.CoreSvcCli{
				Mgmt: &coresvccli.MgmtService{
					PrivateLinkServiceMigration: client,
				},
			},
		},
	}
}

func newMigratePrivateLinkCreateReqJSON() string {
	req := &mgmtv1.CreatePrivateLinkServiceMigrationRequest{
		Migration: &mgmtv1.PrivateLinkServiceMigration{
			SourceClusterId: 1,
			TargetClusterId: 2,
			MigrationTidbGroups: []*mgmtv1.PrivateLinkServiceMigration_MigrationTidbGroup{
				{
					SourceTidbGroupId: "src-group",
					TargetTidbGroupId: "dst-group",
				},
			},
		},
	}

	raw, err := protojson.Marshal(req)
	Expect(err).NotTo(HaveOccurred())
	return string(raw)
}

var _ = Describe("MigrateClusterPrivateLink", func() {
	var (
		ctx      context.Context
		instance *opent.Instance
	)

	BeforeEach(func() {
		ctx = context.Background()
		instance = &opent.Instance{
			Params: map[opent.ParamName]string{
				opent.ParamMigratePrivateLinkRequest: newMigratePrivateLinkCreateReqJSON(),
			},
			Outputs: map[opent.OutputName]string{},
		}
	})

	It("stores migration id and target service plan on Start", func() {
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockPrivateLinkServiceMigrationServiceClient(ctrl)
		mockClient.EXPECT().
			CreatePrivateLinkServiceMigration(gomock.Any(), gomock.Any()).
			Return(&mgmtv1.PrivateLinkServiceMigration{
				Id:                123,
				TargetServicePlan: v1beta1.ServicePlan_Premium,
			}, nil)
		action := newMigratePrivateLinkActionWithClient(mockClient)

		result, err := action.Start(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(result.Result).To(Equal(opent.ActionStatusRunning))
		Expect(result.InstanceOutputs[opent.OutputPrivateLinkMigrationID]).To(Equal("123"))
		Expect(result.InstanceOutputs[opent.OutputPrivateLinkMigrationTargetServicePlan]).To(Equal("Premium"))
	})

	It("passes target service plan to GetPrivateLinkServiceMigration in Result", func() {
		var capturedReq *mgmtv1.GetPrivateLinkServiceMigrationRequest
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockPrivateLinkServiceMigrationServiceClient(ctrl)
		mockClient.EXPECT().
			GetPrivateLinkServiceMigration(gomock.Any(), gomock.Any()).
			DoAndReturn(func(_ context.Context, in *mgmtv1.GetPrivateLinkServiceMigrationRequest, _ ...grpc.CallOption) (*mgmtv1.PrivateLinkServiceMigration, error) {
				capturedReq = in
				return &mgmtv1.PrivateLinkServiceMigration{
					State: mgmtv1.PrivateLinkServiceMigration_RUNNING,
				}, nil
			})
		action := newMigratePrivateLinkActionWithClient(mockClient)

		instance.Outputs[opent.OutputPrivateLinkMigrationID] = "222"
		instance.Outputs[opent.OutputPrivateLinkMigrationTargetServicePlan] = "Essential"

		result, err := action.Result(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(result.Result).To(Equal(opent.ActionStatusRunning))
		Expect(capturedReq).NotTo(BeNil())
		Expect(capturedReq.Id).To(Equal(uint64(222)))
		Expect(capturedReq.TargetServicePlan).To(Equal(v1beta1.ServicePlan_Essential))
	})

	It("returns unknown when target service plan output is missing in Result", func() {
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockPrivateLinkServiceMigrationServiceClient(ctrl)
		action := newMigratePrivateLinkActionWithClient(mockClient)
		instance.Outputs[opent.OutputPrivateLinkMigrationID] = "222"

		result, err := action.Result(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(result.Result).To(Equal(opent.ActionStatusUnknown))
		Expect(result.ActionOutput).To(ContainSubstring("missing output 'privatelink_migration_target_service_plan'"))
	})

	It("returns unknown when target service plan output is invalid in Result", func() {
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockPrivateLinkServiceMigrationServiceClient(ctrl)
		action := newMigratePrivateLinkActionWithClient(mockClient)
		instance.Outputs[opent.OutputPrivateLinkMigrationID] = "222"
		instance.Outputs[opent.OutputPrivateLinkMigrationTargetServicePlan] = "InvalidPlan"

		result, err := action.Result(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(result.Result).To(Equal(opent.ActionStatusUnknown))
		Expect(result.ActionOutput).To(ContainSubstring("invalid output 'privatelink_migration_target_service_plan'"))
	})

	It("passes target service plan to Get and Cancel in OnCancel", func() {
		var capturedGetReq *mgmtv1.GetPrivateLinkServiceMigrationRequest
		var capturedCancelReq *mgmtv1.CancelPrivateLinkServiceMigrationRequest
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockPrivateLinkServiceMigrationServiceClient(ctrl)
		mockClient.EXPECT().
			GetPrivateLinkServiceMigration(gomock.Any(), gomock.Any()).
			DoAndReturn(func(_ context.Context, in *mgmtv1.GetPrivateLinkServiceMigrationRequest, _ ...grpc.CallOption) (*mgmtv1.PrivateLinkServiceMigration, error) {
				capturedGetReq = in
				return &mgmtv1.PrivateLinkServiceMigration{
					State: mgmtv1.PrivateLinkServiceMigration_RUNNING,
				}, nil
			})
		mockClient.EXPECT().
			CancelPrivateLinkServiceMigration(gomock.Any(), gomock.Any()).
			DoAndReturn(func(_ context.Context, in *mgmtv1.CancelPrivateLinkServiceMigrationRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
				capturedCancelReq = in
				return &emptypb.Empty{}, nil
			})
		action := newMigratePrivateLinkActionWithClient(mockClient)

		instance.Outputs[opent.OutputPrivateLinkMigrationID] = "333"
		instance.Outputs[opent.OutputPrivateLinkMigrationTargetServicePlan] = "Dedicated"

		canceled, err := action.OnCancel(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(canceled).To(BeFalse())
		Expect(capturedGetReq).NotTo(BeNil())
		Expect(capturedGetReq.Id).To(Equal(uint64(333)))
		Expect(capturedGetReq.TargetServicePlan).To(Equal(v1beta1.ServicePlan_Dedicated))
		Expect(capturedCancelReq).NotTo(BeNil())
		Expect(capturedCancelReq.Id).To(Equal(uint64(333)))
		Expect(capturedCancelReq.TargetServicePlan).To(Equal(v1beta1.ServicePlan_Dedicated))
	})

	It("returns error when target service plan output is missing in OnCancel", func() {
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockPrivateLinkServiceMigrationServiceClient(ctrl)
		action := newMigratePrivateLinkActionWithClient(mockClient)
		instance.Outputs[opent.OutputPrivateLinkMigrationID] = "333"

		canceled, err := action.OnCancel(ctx, instance)
		Expect(canceled).To(BeFalse())
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("missing output 'privatelink_migration_target_service_plan'"))
	})
})
