package br

import (
	"context"
	"fmt"
	"strconv"

	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	eventdata "github.com/tidbcloud/eventbus-datastruct/event"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/br"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/displayevent"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	wf "github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

type restoreAdapterClassicBase struct {
	workflow         wf.Workflow
	clusterRepo      cluster.Repo
	regionRepo       region.Repo
	regionalCli      regionalserver.Client
	transaction      db.Transactioner
	restoreRepo      br.RestoreRepo
	eventCommitter   eventbus.WALCommitter
	resourcePoolRepo resourcepoolrepo.Repo
}

func (a *restoreAdapterClassicBase) writeRestoreStartedDisplayEvent(
	ctx context.Context,
	restore *entity.Restore,
) {
	displayEv := newRestoreDisplayEvent(ctx, restore, eventdata.EventStatusStarted, "Restore started.")
	displayevent.Write(ctx, a.eventCommitter, displayEv)
}

func (a *restoreAdapterClassicBase) GetRestoreRunningStatus(
	ctx context.Context,
	clusterId uint64,
	restore *entity.Restore,
) (*globalngv1.RestoreStatusResponse, bizerr.BizError) {
	targetCluster, berr := a.clusterRepo.Get(ctx, clusterId, false)
	if berr != nil {
		return nil, berr.Chain("failed to get cluster")
	}

	targetRegion, berr := a.regionRepo.Get(ctx, targetCluster.RegionId)
	if berr != nil {
		return nil, berr.Chain("failed to get region")
	}

	req := &clusterngv1beta.GetClassicRestoreRequest{
		TargetSharedPoolId: targetCluster.SharedPoolId,
		RestoreId:          fmt.Sprintf("%d", restore.TargetClusterId),
	}

	restoreResp, berr := a.regionalCli.GetClassicRestore(ctx, targetRegion, req)
	if berr != nil {
		tilog.Error(
			ctx,
			"failed to get classic restore in cluster service",
			zap.Error(berr),
			zap.Any("request", req),
		)
		return nil, berr.Chain("failed to get classic restore")
	}

	tilog.Info(
		ctx,
		"get classic restore in cluster service",
		zap.Uint64("cluster id", clusterId),
		zap.Any("restore", restoreResp),
	)

	switch restoreResp.Status {
	case clusterngv1beta.ClassicRestore_PROVISIONING:
		return &globalngv1.RestoreStatusResponse{
			State:   globalngv1.RestoreState_RESTORE_STATE_PENDING,
			Message: "Restore is pending for provisioning resources",
		}, nil
	case clusterngv1beta.ClassicRestore_DATA_RESTORING:
		progress := min(int32(restoreResp.DataRestoreTotalProgressPercentage), 90)
		return &globalngv1.RestoreStatusResponse{
			State: globalngv1.RestoreState_RESTORE_STATE_RUNNING,
			Message: fmt.Sprintf(
				"Data restore is running, step: %s, progress: %d%%",
				restoreResp.DataRestoreStep,
				restoreResp.DataRestoreStepProgressPercentage,
			),
			Progress: progress,
		}, nil
	case clusterngv1beta.ClassicRestore_DATA_RESTORING_ERROR:
		return &globalngv1.RestoreStatusResponse{
			State:   globalngv1.RestoreState_RESTORE_STATE_FAILED,
			Message: "Restore is failed during data restoring: " + restoreResp.Message,
		}, nil
	case clusterngv1beta.ClassicRestore_POST_RESTORE_ACTION:
		return &globalngv1.RestoreStatusResponse{
			State:    globalngv1.RestoreState_RESTORE_STATE_RUNNING,
			Message:  "Data restore is done, waiting for cluster service to execute post-restore operations",
			Progress: 95,
		}, nil
	case clusterngv1beta.ClassicRestore_SUCCEED:
		return &globalngv1.RestoreStatusResponse{
			State:    globalngv1.RestoreState_RESTORE_STATE_RUNNING,
			Message:  "Restore is done in regional control plane",
			Progress: 99,
		}, nil
	case clusterngv1beta.ClassicRestore_FAILED:
		return &globalngv1.RestoreStatusResponse{
			State:   globalngv1.RestoreState_RESTORE_STATE_FAILED,
			Message: "Restore is failed: " + restoreResp.Message,
		}, nil
	}
	tilog.Error(
		ctx,
		"unexpected restore status in cluster service: "+restoreResp.Status.String(),
		zap.String("cluster id", fmt.Sprintf("%d", clusterId)),
		zap.Any("restore", restoreResp),
	)
	return nil, bizerr.New(
		bizerr.GeneralInternalErrorCode,
		"%s",
		"unexpected restore status in cluster service: "+restoreResp.Status.String(),
	)
}

func (a *restoreAdapterClassicBase) StopRestore(ctx context.Context, req *globalngv1.StopRestoreClusterRequest, dagName string) bizerr.BizError {
	err := a.workflow.DeleteDagInstancesWithDagId(ctx, req.TargetClusterId, dagName)
	if err != nil {
		tilog.Error(
			ctx,
			fmt.Sprintf("failed to delete restore dag: %s instances", dagName),
			zap.Error(err),
			zap.String("cluster_id", req.TargetClusterId),
		)
		return bizerr.WrapError(err, "failed to delete restore dag: %s instances", dagName)
	}
	tilog.Info(ctx, fmt.Sprintf("delete restore dag: %s instances success", dagName), zap.String("cluster_id", req.TargetClusterId))

	clusterId, err := strconv.ParseUint(req.TargetClusterId, 10, 64)
	if err != nil {
		return bizerr.WrapError(err, "failed to parse target cluster id")
	}
	targetCluster, berr := a.clusterRepo.Get(ctx, clusterId, true)
	if berr != nil {
		return berr.Chain("failed to get cluster")
	}

	targetRegion, berr := a.regionRepo.Get(ctx, targetCluster.RegionId)
	if berr != nil {
		return berr.Chain("failed to get region")
	}

	berr = a.regionalCli.DeleteClassicRestore(ctx, targetRegion, &clusterngv1beta.DeleteClassicRestoreRequest{
		TargetSharedPoolId: targetCluster.SharedPoolId,
		RestoreId:          req.TargetClusterId,
	})
	if berr != nil {
		tilog.Error(
			ctx,
			"failed to delete classic restore in cluster service",
			zap.Error(berr),
			zap.String("cluster_id", req.TargetClusterId),
		)
		return berr.Chain("failed to delete classic restore")
	}
	tilog.Info(
		ctx,
		"delete classic restore in cluster service success",
		zap.String("cluster_id", req.TargetClusterId),
	)
	return nil
}
