package action

import (
	"errors"
	"fmt"
	"strconv"
	"time"

	"braces.dev/errtrace"
	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"
	"gorm.io/gorm"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/dataflow"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/dedicatedglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

// NewFinalBackupAction creates a new final backup action

type DedicatedFinalBackupAction struct {
	dedicatedCli     dedicatedglobal.DedicatedGlobal
	dataflowCli      dataflow.DataFlow
	customStatusRepo repository.CustomStatusRepository
}

func NewDedicatedFinalBackupAction(
	dedicatedCli dedicatedglobal.DedicatedGlobal,
	dataflowCli dataflow.DataFlow,
	customStatusRepo repository.CustomStatusRepository,
) *DedicatedFinalBackupAction {
	return &DedicatedFinalBackupAction{
		dedicatedCli:     dedicatedCli,
		dataflowCli:      dataflowCli,
		customStatusRepo: customStatusRepo,
	}
}

func (c *DedicatedFinalBackupAction) Name() string {
	return constant.TaskDedicatedClusterFinalBackup
}

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

	tilog.Info(cctx, "final backup action start")

	needFinalBackup, ok := ctx.GetVar(constant.KeyFinalBackup)
	if !ok || needFinalBackup != "true" {
		tilog.Info(cctx, "no need to do final backup, skip it")
		return nil
	}

	// get cluster id
	requestJson, ok := ctx.GetVar(constant.KeyRequest)
	if !ok {
		return errtrace.Wrap(errors.New("delete dedicated request is not found"))
	}

	var request globaldedicatedv1.DeleteClusterRequest
	if err := protojson.Unmarshal([]byte(requestJson), &request); err != nil {
		return fmt.Errorf("unmarshal delete dedicated request failed: %w", err)
	}

	// Only make final backups for poc clusters in normal/scaling/pausing/paused/resuming status
	dedicatedCluster, err := c.dedicatedCli.GetCluster(
		cctx,
		&globaldedicatedv1.GetClusterRequest{ClusterId: request.ClusterId},
	)
	if err != nil {
		return fmt.Errorf("get dedicated cluster failed: %w, cluster id: %d", err, request.ClusterId)
	}

	switch dedicatedCluster.State {
	case globaldedicatedv1.Cluster_CREATING,
		globaldedicatedv1.Cluster_DELETED,
		globaldedicatedv1.Cluster_UNAVAILABLE:
		tilog.Info(cctx, "skip final backup", zap.String("cluster state", dedicatedCluster.State.String()))
		return nil
	case globaldedicatedv1.Cluster_AVAILABLE,
		globaldedicatedv1.Cluster_MODIFYING,
		globaldedicatedv1.Cluster_SCALING:
		// do nothing
	case globaldedicatedv1.Cluster_PAUSED:
		tilog.Info(cctx, "cluster is paused, need resume first")
		_, err = c.dedicatedCli.ResumeCluster(
			cctx,
			&globaldedicatedv1.ResumeClusterRequest{ClusterId: request.ClusterId},
		)
		if err != nil {
			return fmt.Errorf("resume cluster failed: %w, cluster id: %d", err, request.ClusterId)
		}
		return errors.New("cluster is paused, need resume first")
	case globaldedicatedv1.Cluster_PAUSING,
		globaldedicatedv1.Cluster_RESUMING:
		tilog.Info(
			cctx,
			"cluster is not ready, need wait",
			zap.String("cluster state", dedicatedCluster.State.String()),
		)
		time.Sleep(10 * time.Second)
		return errors.New("cluster is not ready, need wait")
	}

	// create final backup or get final backup id
	backupId, err := c.dataflowCli.CreateFinalBackupIgnoreExist(cctx, request.ClusterId)
	if err != nil {
		return fmt.Errorf("create final backup failed: %w, cluster id: %d", err, request.ClusterId)
	}

	// check final backup status from custom status
	_, err = c.customStatusRepo.GetClusterCustomStatusByName(
		cctx,
		request.ClusterId,
		strconv.FormatUint(backupId, 10),
		tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_BACKUP,
	)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			tilog.Info(
				cctx,
				"final backup action end",
				zap.Uint64("cluster id", request.ClusterId),
				zap.Uint64("backup id", backupId),
			)
			return nil
		}

		return fmt.Errorf("get final backup status failed: %w, cluster id: %d", err, request.ClusterId)
	}

	// wait for final backup finished
	tilog.Warn(
		cctx,
		"final backup is not finished yet",
		zap.Uint64("cluster id", request.ClusterId),
		zap.Uint64("backup id", backupId),
	)
	return fmt.Errorf("workflow is not finished yet, need to wait for final backup finished")
}
