package orchestrator

import (
	"context"
	"errors"
	"fmt"
	"strconv"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	grpcmetadata "google.golang.org/grpc/metadata"

	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

type DeleteClusterCustomStatus struct {
	service tidbmgmtservicev1.ClusterCustomStatusServiceServer
}

func NewDeleteClusterCustomStatus(
	service tidbmgmtservicev1.ClusterCustomStatusServiceServer,
) *DeleteClusterCustomStatus {
	return &DeleteClusterCustomStatus{service: service}
}

func (s *DeleteClusterCustomStatus) Name() string {
	return actionDeleteCustomStatus
}

func (s *DeleteClusterCustomStatus) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)
	defer span.End()

	statusIdStr, ok := ctx.GetVar(DagKeyClusterCustomStatusId)
	if !ok {
		tilog.Warn(cctx, "not found cluster custom status id")
		return nil
	}
	clusterIdStr, ok := ctx.GetVar(DagKeyClusterId)
	if !ok {
		return errors.New("not found cluster key")
	}

	statusId, err := strconv.ParseUint(statusIdStr, 10, 64)
	if err != nil {
		tilog.Error(cctx, "parse status id failed", zap.String("status id", statusIdStr), zap.Error(err))
		return err
	}
	clusterId, err := strconv.ParseUint(clusterIdStr, 10, 64)
	if err != nil {
		tilog.Error(cctx, "parse cluster id failed", zap.String("cluster id", clusterIdStr), zap.Error(err))
		return err
	}

	_, err = s.service.DeleteClusterCustomStatus(cctx, &tidbmgmtservicev1.DeleteClusterCustomStatusRequest{
		StatusId:  statusId,
		ClusterId: clusterId,
	})

	return err
}

type WaitClusterNormalAction struct {
	openAPIDedicatedClusterServer openapidedicatedv1beta1.ClusterServiceServer
}

func NewWaitClusterNormalAction(
	openAPIClusterCli openapidedicatedv1beta1.ClusterServiceServer,
) *WaitClusterNormalAction {
	return &WaitClusterNormalAction{
		openAPIDedicatedClusterServer: openAPIClusterCli,
	}
}

func (s *WaitClusterNormalAction) Name() string {
	return actionWaitClusterActive
}

func (s *WaitClusterNormalAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)
	defer span.End()

	clusterId, ok := ctx.GetVar(DagKeyClusterId)
	if !ok {
		return errors.New("not found cluster key")
	}

	c, err := s.openAPIDedicatedClusterServer.GetCluster(cctx, &openapidedicatedv1beta1.GetClusterRequest{
		ClusterId: clusterId,
	})
	if err != nil {
		return err
	}

	if c.State != openapicommonv1beta1.ClusterState_ACTIVE {
		return fmt.Errorf("wait cluster %s to be normal", clusterId)
	}

	return nil
}

type OpenAPIAction struct {
	name   string
	action func(ctx context.Context, requestStr string) error
}

func NewOpenAPIAction(name ActionName, action func(ctx context.Context, requestStr string) error) *OpenAPIAction {
	return &OpenAPIAction{
		name:   string(name),
		action: action,
	}
}

func (a *OpenAPIAction) Name() string {
	return a.name
}

func (a *OpenAPIAction) ParameterNew() any {
	return &OpenAPIActionParameter{}
}

type OpenAPIActionParameter struct {
	RequestBody string `json:"request_body"`
}

func (a *OpenAPIAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)
	defer span.End()
	statusId, ok := ctx.GetVar(DagKeyClusterCustomStatusId)
	if ok {
		cctx = grpcmetadata.AppendToOutgoingContext(cctx, metadata.IgnoreCustomStatusIDKey, statusId)
	}

	tilog.Info(cctx, "fetch request body")

	actionParam, ok := params.(*OpenAPIActionParameter)
	if !ok || actionParam.RequestBody == "" {
		return errors.New("empty request body")
	}

	return a.action(cctx, actionParam.RequestBody)
}
