package br

import (
	"context"
	"errors"
	"fmt"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	braction "github.com/tidbcloud/cluster-service-ng/domain/workflow/action/br"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/dag"
	"github.com/tidbcloud/cluster-service-ng/pkg/clustergateway"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/executorext"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

type lwRestoreCrossSharedPoolAdaptor struct {
	clustergatewayCli clustergateway.Client
	sharedPoolRepo    repo.SharedPoolRepo
}

func NewLwRestoreCrossSharedPoolAdaptor(clustergatewayCli clustergateway.Client, sharedPoolRepo repo.SharedPoolRepo) LightweightRestoreAdaptor {
	return &lwRestoreCrossSharedPoolAdaptor{
		sharedPoolRepo:    sharedPoolRepo,
		clustergatewayCli: clustergatewayCli,
	}
}

func (a *lwRestoreCrossSharedPoolAdaptor) GetCreationDagMeta(restoreId, targetSharedPoolId string) *LightweightRestoreDagMeta {
	return &LightweightRestoreDagMeta{
		DagName:         dag.PlanCreateLightweightXRestore,
		DagInstanceName: fmt.Sprintf("create-lw-x-restore-%s", restoreId),
		DagInstanceFilters: []executorext.DagInsFilter{
			{ArgKey: workflow.RestoreIdKey, ArgValues: []string{restoreId}},
		},
	}
}

func (a *lwRestoreCrossSharedPoolAdaptor) GetDeletionDagMeta(restoreId, targetSharedPoolId string) *LightweightRestoreDagMeta {
	return &LightweightRestoreDagMeta{
		DagName:         dag.PlanDeleteLightweightXRestore,
		DagInstanceName: fmt.Sprintf("delete-lw-x-restore-%s", restoreId),
		DagInstanceFilters: []executorext.DagInsFilter{
			{ArgKey: workflow.RestoreIdKey, ArgValues: []string{restoreId}},
		},
	}
}

func (a *lwRestoreCrossSharedPoolAdaptor) GetRestoreInRunning(
	ctx context.Context,
	restore *entity.LightweightRestore,
) (*GetLightweightRestoreOutput, error) {
	switch restore.GetLastAction() {
	case "", schema.LightweightRestoreActionScaleTiKV, schema.LightweightRestoreActionCreateTiBR, schema.LightweightRestoreActionGrantAccess:
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_PROVISIONING,
			Message: "provisioning resource for restore, current action is " + restore.GetLastAction(),
		}, nil
	case schema.LightweightRestoreActionExecPackedBackup, schema.LightweightRestoreActionExecRestorePacked:
		return a.getDataRestoringOutput(ctx, restore)
	case schema.LightweightRestoreActionRevokeAccess, schema.LightweightRestoreActionRestartTiDB, schema.LightweightRestoreActionDeleteTiBR:
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_POST_RESTORE_ACTION,
			Message: "data restoring done, performing post restore action, current action is " + restore.GetLastAction(),
		}, nil
	default:
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_LIGHTWEIGHT_RESTORE_STATUS_UNSPECIFIED,
			Message: "unknown action in running status, should not happen",
		}, nil
	}
}

// Notice: no data restoring progress from kernel, so we use very raw progress percentage
func (a *lwRestoreCrossSharedPoolAdaptor) getDataRestoringOutput(ctx context.Context, restore *entity.LightweightRestore) (*GetLightweightRestoreOutput, error) {
	targetSharedPool, err := a.sharedPoolRepo.Load(ctx, restore.TargetSharedPoolID, false)
	if err != nil {
		return nil, fmt.Errorf("get target sharedPool %s failed: %s", restore.TargetSharedPoolID, err.Error())
	}

	packResp, err := a.clustergatewayCli.GetTiBRTask(ctx, targetSharedPool,
		tioperator.TiBRCRNameOfRestore(restore.RestoreID), braction.KernelPackBackupTaskId)
	if err != nil {
		if errors.Is(err, clustergateway.ErrBRTaskNotFound) {
			return &GetLightweightRestoreOutput{
				Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
				Message:                       "wait for the kernel pack backup task created",
				DataRestoreProgressPercentage: 0,
			}, nil
		}
		tilog.Error(ctx, "get kernel pack backup task failed",
			zap.Error(err), zap.String("restoreId", restore.RestoreID), zap.String("targetSharedPool", restore.TargetSharedPoolID))
		return nil, fmt.Errorf("get kernel pack backup task failed: %s", err.Error())
	}

	tilog.Info(ctx, "get kernel pack backup task",
		zap.String("restoreId", restore.RestoreID), zap.String("targetSharedPool", restore.TargetSharedPoolID), zap.Any("packResp", packResp))

	switch packResp.State {
	case clustergateway.TaskStatePending:
		return &GetLightweightRestoreOutput{
			Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
			Message:                       "kernel is pending for pack backup",
			DataRestoreProgressPercentage: 0,
		}, nil
	case clustergateway.TaskStateInit:
		return &GetLightweightRestoreOutput{
			Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
			Message:                       "wait for the kernel pack backup task initialized",
			DataRestoreProgressPercentage: 10,
		}, nil
	case clustergateway.TaskStateRunning:
		return &GetLightweightRestoreOutput{
			Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
			Message:                       "kernel is running for pack backup",
			DataRestoreProgressPercentage: 40,
		}, nil
	case clustergateway.TaskStateDone:
		return a.getRestorePackedBackupOutput(ctx, restore, targetSharedPool)
	case clustergateway.TaskStateError:
		message := "kernel pack backup task error: " + packResp.Error
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_DATA_RESTORING_ERROR,
			Message: message,
			Failure: kernelRestoreFailure("KERNEL_TASK_FAILED", message),
		}, nil
	default:
		message := "unknown state for kernel pack backup task"
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_DATA_RESTORING_ERROR,
			Message: message,
			Failure: &apiv1.RestoreFailure{Source: apiv1.RestoreFailureSource_KERNEL, Code: "KERNEL_TASK_STATE_UNKNOWN", Message: message},
		}, nil
	}
}

func (a *lwRestoreCrossSharedPoolAdaptor) getRestorePackedBackupOutput(ctx context.Context, restore *entity.LightweightRestore, targetSharedPool *entity.SharedPool) (*GetLightweightRestoreOutput, error) {
	restoreResp, err := a.clustergatewayCli.GetTiBRTask(ctx, targetSharedPool,
		tioperator.TiBRCRNameOfRestore(restore.RestoreID), braction.KernelRestorePackedTaskId)
	if err != nil {
		if errors.Is(err, clustergateway.ErrBRTaskNotFound) {
			return &GetLightweightRestoreOutput{
				Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
				Message:                       "wait for the kernel restore packed task created",
				DataRestoreProgressPercentage: 50,
			}, nil
		}
		tilog.Error(ctx, "get kernel restore packed task failed",
			zap.Error(err), zap.String("restoreId", restore.RestoreID), zap.String("targetSharedPool", restore.TargetSharedPoolID))
		return nil, fmt.Errorf("get kernel restore packed task failed: %s", err.Error())
	}

	tilog.Info(ctx, "get kernel restore packed task",
		zap.String("restoreId", restore.RestoreID), zap.String("targetSharedPool", restore.TargetSharedPoolID), zap.Any("restoreResp", restoreResp))

	switch restoreResp.State {
	case clustergateway.TaskStatePending:
		return &GetLightweightRestoreOutput{
			Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
			Message:                       "kernel is pending for restore packed",
			DataRestoreProgressPercentage: 50,
		}, nil
	case clustergateway.TaskStateInit:
		return &GetLightweightRestoreOutput{
			Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
			Message:                       "wait for the kernel restore packed task initialized",
			DataRestoreProgressPercentage: 60,
		}, nil
	case clustergateway.TaskStateRunning:
		return &GetLightweightRestoreOutput{
			Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
			Message:                       "kernel is running for restore packed",
			DataRestoreProgressPercentage: 90,
		}, nil
	case clustergateway.TaskStateDone:
		restoreBytes := uint64(0)
		if info, ok := restoreResp.Info["restore_bytes"]; ok {
			restoreBytes = info.(uint64)
		}
		return &GetLightweightRestoreOutput{
			Status:                        apiv1.LightweightRestoreStatus_DATA_RESTORING,
			Message:                       "kernel restore packed task done",
			DataRestoreProgressPercentage: 100,
			DataRestoreBytes:              restoreBytes,
		}, nil
	case clustergateway.TaskStateError:
		message := "kernel restore packed task error: " + restoreResp.Error
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_DATA_RESTORING_ERROR,
			Message: message,
			Failure: kernelRestoreFailure("KERNEL_TASK_FAILED", message),
		}, nil
	default:
		message := "unknown state for kernel restore packed task"
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_DATA_RESTORING_ERROR,
			Message: message,
			Failure: &apiv1.RestoreFailure{Source: apiv1.RestoreFailureSource_KERNEL, Code: "KERNEL_TASK_STATE_UNKNOWN", Message: message},
		}, nil
	}
}

func (a *lwRestoreCrossSharedPoolAdaptor) GetPostActions(ctx context.Context, restore *entity.LightweightRestore) (*GetLightweightRestorePostActionsOutput, error) {
	result, err := BuildGlobalCompensateResult(ctx, a.sharedPoolRepo, restore.TargetSharedPoolID)
	if err != nil {
		return nil, err
	}

	resp := &GetLightweightRestorePostActionsOutput{
		GetLightweightRestorePostActionsResponse: &apiv1.GetLightweightRestorePostActionsResponse{
			ExecSql:         result.SQL,
			CloudStorageUri: result.CloudStorageURI,
		},
	}
	return resp, nil
}
