package clusteraction

import (
	"context"
	"fmt"
	"strconv"

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

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
)

var (
	ActionCreateDataSynchronization = opent.ActionMetadata{
		Name: "CreateDataSynchronization",
		Type: opent.ActionTypeAuto,
	}
)

var (
	_ actions.Action = &createDataSynchronization{}
)

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

func NewCreateDataSynchronization(comps *actions.Components) actions.AutoAction {
	return &createDataSynchronization{comps: comps}
}

func (a *createDataSynchronization) Metadata() opent.ActionMetadata {
	return ActionCreateDataSynchronization
}

func (a *createDataSynchronization) PreCheck(ctx context.Context, instance *opent.Instance) error {
	_, err := a.getParam(instance)
	return err
}

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

	resp, err := a.comps.CoreSvc.Mgmt.DataSynchronization.CreateDataSynchronization(ctx, req)
	if err != nil {
		return actions.FailedActionResult(err), nil
	}

	return actions.RunningActionResult(fmt.Sprintf("sent request to create data synchronization: %d", resp.GetId()), map[opent.OutputName]string{
		opent.OutputDataSynchronizationID: strconv.FormatUint(resp.GetId(), 10),
	}), nil
}

func (a *createDataSynchronization) Result(ctx context.Context, instance *opent.Instance) (*actions.ActionResult, error) {
	dsIDStr, ok := instance.GetOutput(opent.OutputDataSynchronizationID)
	if !ok || dsIDStr == "" {
		return actions.NewActionResult(opent.ActionStatusUnknown, fmt.Sprintf("missing output '%s'", opent.OutputDataSynchronizationID), nil), nil
	}

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

	resp, err := a.comps.CoreSvc.Mgmt.DataSynchronization.GetDataSynchronization(ctx, &mgmtv1.GetDataSynchronizationRequest{
		Id: dsID,
	})
	if err != nil {
		return actions.NewActionResult(opent.ActionStatusUnknown, err.Error(), nil), nil
	}

	switch resp.GetState() {
	case mgmtv1.DataSynchronization_SUCCEEDED:
		return actions.SucceededActionResult(nil), nil
	case mgmtv1.DataSynchronization_FAILED:
		return actions.FailedActionResult(fmt.Errorf("data synchronization failed")), nil
	default:
		return actions.RunningActionResult(fmt.Sprintf("data synchronization state is '%s'", resp.GetState()), nil), nil
	}
}

func (a *createDataSynchronization) getParam(instance *opent.Instance) (*mgmtv1.CreateDataSynchronizationRequest, error) {
	sourceClusterID, err := instance.GetUintParam(opent.ParamClusterID)
	if err != nil {
		return nil, fmt.Errorf("param source_cluster_id is required")
	}

	targetClusterID, err := instance.GetUintParam(opent.ParamTargetClusterID)
	if err != nil {
		return nil, fmt.Errorf("param target_cluster_id is required")
	}

	name, ok := instance.GetParam(opent.ParamDataSyncName)
	if !ok || name == "" {
		return nil, fmt.Errorf("param data_sync_name is required")
	}

	ds := &mgmtv1.DataSynchronization{
		SourceClusterId: sourceClusterID,
		TargetClusterId: targetClusterID,
		Name:            name,
	}

	// optional fields
	if v, ok := instance.GetParam(opent.ParamTargetTiDBGroupID); ok && v != "" {
		val, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid target_tidb_group_id: %s", v)
		}
		ds.TargetTidbGroupId = val
	}

	if v, ok := instance.GetParam(opent.ParamDataSyncDescription); ok && v != "" {
		ds.Description = v
	}

	if v, ok := instance.GetParam(opent.ParamDataSyncRCU); ok && v != "" {
		val, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid data_sync_rcu: %s", v)
		}
		ds.Rcu = val
	}

	if v, ok := instance.GetParam(opent.ParamDataSyncStartTSO); ok && v != "" {
		ds.StartTso = v
	}

	if v, ok := instance.GetParam(opent.ParamTargetClusterUsername); ok && v != "" {
		ds.TargetClusterUsername = v
	}

	if v, ok := instance.GetParam(opent.ParamTargetClusterPassword); ok && v != "" {
		ds.TargetClusterPassword = v
	}

	return &mgmtv1.CreateDataSynchronizationRequest{
		DataSynchronization: ds,
	}, nil
}
