package clusteraction

import (
	"context"
	"fmt"

	. "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"
	dediv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/encoding/protojson"
)

func newApplyHeterogeneousDeployActionWithClient(client mgmtv1.ClusterServiceClient) *applyHeterogeneousDeploy {
	return &applyHeterogeneousDeploy{
		comps: &actions.Components{
			CoreSvc: &coresvccli.CoreSvcCli{
				Mgmt: &coresvccli.MgmtService{
					Cluster: client,
				},
			},
		},
	}
}

func newApplyHeterogeneousDeployReqJSON() string {
	req := &mgmtv1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               3,
		ComponentType:           dediv1.ComponentType_TIDB,
		CurrentInstanceType:     "n2-standard-4",
		AlternativeInstanceType: "n2-standard-8",
		TidbGroupId:             4,
	}

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

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

	BeforeEach(func() {
		ctx = context.Background()
		instance = &opent.Instance{
			Params: map[opent.ParamName]string{
				opent.ParamClusterID:                       "3",
				opent.ParamApplyHeterogeneousDeployRequest: newApplyHeterogeneousDeployReqJSON(),
			},
			Outputs: map[opent.OutputName]string{},
		}
	})

	It("fails PreCheck when cluster id is missing", func() {
		ctrl := gomock.NewController(GinkgoT())
		action := newApplyHeterogeneousDeployActionWithClient(coresvccli.NewMockClusterServiceClient(ctrl))
		delete(instance.Params, opent.ParamClusterID)

		err := action.PreCheck(ctx, instance)
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("param cluster_id is not found"))
	})

	It("fails PreCheck when request param is missing", func() {
		ctrl := gomock.NewController(GinkgoT())
		action := newApplyHeterogeneousDeployActionWithClient(coresvccli.NewMockClusterServiceClient(ctrl))
		delete(instance.Params, opent.ParamApplyHeterogeneousDeployRequest)

		err := action.PreCheck(ctx, instance)
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("missing required param"))
	})

	It("fails PreCheck when request param is invalid", func() {
		ctrl := gomock.NewController(GinkgoT())
		action := newApplyHeterogeneousDeployActionWithClient(coresvccli.NewMockClusterServiceClient(ctrl))
		instance.Params[opent.ParamApplyHeterogeneousDeployRequest] = "{"

		err := action.PreCheck(ctx, instance)
		Expect(err).To(HaveOccurred())
		Expect(err.Error()).To(ContainSubstring("unmarshal param failed"))
	})

	It("calls ApplyGKEHeterogeneousDeploy on Start", func() {
		var capturedReq *mgmtv1.ApplyGKEHeterogeneousDeployRequest
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockClusterServiceClient(ctrl)
		mockClient.EXPECT().
			ApplyGKEHeterogeneousDeploy(gomock.Any(), gomock.Any()).
			DoAndReturn(func(_ context.Context, in *mgmtv1.ApplyGKEHeterogeneousDeployRequest, _ ...grpc.CallOption) (*mgmtv1.GKEHeterogeneousDeploy, error) {
				capturedReq = in
				return &mgmtv1.GKEHeterogeneousDeploy{}, nil
			})
		action := newApplyHeterogeneousDeployActionWithClient(mockClient)

		result, err := action.Start(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(result.Result).To(Equal(opent.ActionStatusSucceeded))
		Expect(capturedReq).NotTo(BeNil())
		Expect(capturedReq.ClusterId).To(Equal(uint64(3)))
		Expect(capturedReq.ComponentType).To(Equal(dediv1.ComponentType_TIDB))
		Expect(capturedReq.CurrentInstanceType).To(Equal("n2-standard-4"))
		Expect(capturedReq.AlternativeInstanceType).To(Equal("n2-standard-8"))
		Expect(capturedReq.TidbGroupId).To(Equal(uint64(4)))
	})

	It("returns failed action result when ApplyGKEHeterogeneousDeploy fails", func() {
		ctrl := gomock.NewController(GinkgoT())
		mockClient := coresvccli.NewMockClusterServiceClient(ctrl)
		mockClient.EXPECT().
			ApplyGKEHeterogeneousDeploy(gomock.Any(), gomock.Any()).
			Return(nil, fmt.Errorf("apply failed"))
		action := newApplyHeterogeneousDeployActionWithClient(mockClient)

		result, err := action.Start(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(result.Result).To(Equal(opent.ActionStatusFailed))
		Expect(result.ActionOutput).To(ContainSubstring("apply failed"))
	})

	It("returns succeeded result directly", func() {
		ctrl := gomock.NewController(GinkgoT())
		action := newApplyHeterogeneousDeployActionWithClient(coresvccli.NewMockClusterServiceClient(ctrl))

		result, err := action.Result(ctx, instance)
		Expect(err).NotTo(HaveOccurred())
		Expect(result.Result).To(Equal(opent.ActionStatusSucceeded))
	})
})
