package clusteraction

import (
	"context"
	"fmt"
	"strconv"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	"google.golang.org/protobuf/encoding/protojson"

	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

var (
	ActionCreateMigrationCluster opent.ActionMetadata = opent.ActionMetadata{
		Name: "CreateMigrationCluster",
		Type: opent.ActionTypeAuto,
	}
)

type CreateMigrationCluster struct {
	actions.BaseAction
	comps *actions.Components
}

func NewCreateMigrationCluster(comps *actions.Components) actions.AutoAction {
	return &CreateMigrationCluster{comps: comps}
}

func (a *CreateMigrationCluster) Metadata() opent.ActionMetadata {
	return ActionCreateMigrationCluster
}

func (a *CreateMigrationCluster) PreCheck(ctx context.Context, instance *opent.Instance) error {
	req, err := a.getParam(ctx, instance)
	if err != nil {
		return err
	}

	// dry run the request to validate
	req.ValidateOnly = true
	_, err = a.comps.CoreSvc.Mgmt.ClusterMigration.CreateMigrationCluster(ctx, req)
	if err != nil {
		return fmt.Errorf("validate migrate cluster request failed: %w", err)
	}

	return nil
}

func (a *CreateMigrationCluster) Start(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	req, err := a.getParam(ctx, instance)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	cm, err := a.comps.CoreSvc.Mgmt.ClusterMigration.CreateMigrationCluster(ctx, req)
	if err != nil {
		return actions.FailedActionResult(fmt.Errorf("start migrate cluster failed: %w", err)), nil
	}

	return actions.RunningActionResult("created cluster migration", map[opent.OutputName]string{
		opent.OutputClusterMigrationID:       strconv.FormatUint(cm.Id, 10),
		opent.OutputTargetClusterID:          strconv.FormatUint(cm.TargetClusterId, 10),
		opent.OutputTargetClusterDisplayName: cm.ClusterDisplayName,
	}), nil
}

func (a *CreateMigrationCluster) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	cmStr := instance.Outputs[opent.OutputClusterMigrationID]
	if cmStr == "" {
		return actions.NewActionResult(opent.ActionStatusUnknown, fmt.Sprintf("missing output '%s'", opent.OutputClusterMigrationID), nil), nil
	}

	cmID, err := strconv.ParseUint(cmStr, 10, 64)
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, fmt.Sprintf("invalid output '%s': %s", opent.OutputClusterMigrationID, err), nil), nil
	}

	cm, err := a.comps.CoreSvc.Mgmt.ClusterMigration.GetMigrationCluster(ctx, &mgmtv1.GetMigrationClusterRequest{
		Id: cmID,
	})
	if err != nil {
		return actions.RunningActionResult(fmt.Sprintf("get cluster migration %d failed: %s", cmID, err), nil), nil
	}

	switch cm.State {
	case mgmtv1.MigrationCluster_SUCCEEDED:
		return actions.SucceededActionResult(nil), nil
	case mgmtv1.MigrationCluster_FAILED:
		return actions.FailedActionResult(fmt.Errorf("cluster migration state is '%s'", cm.State.String())), nil
	}

	return actions.RunningActionResult(fmt.Sprintf("cluster migration state is '%s'", cm.State.String()), nil), nil
}

func (a *CreateMigrationCluster) OnCancel(ctx context.Context, instance *opent.Instance) (bool, error) {
	return false, nil
}

func (a *CreateMigrationCluster) getParam(ctx context.Context, instance *opent.Instance) (*mgmtv1.CreateMigrationClusterRequest, error) {
	data := instance.Params[opent.ParamCreateMigrationClusterRequest]
	if data == "" {
		return nil, fmt.Errorf("missing required param")
	}

	req := &mgmtv1.CreateMigrationClusterRequest{}
	err := protojson.Unmarshal([]byte(data), req)
	if err != nil {
		return nil, fmt.Errorf("unmarshal param failed: %w", err)
	}

	return req, nil
}
