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"
	"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 lwRestoreSameSharedPoolAdaptor struct {
	clustergatewayCli clustergateway.Client
	sharedPoolRepo    repo.SharedPoolRepo
}

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

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

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

func (a *lwRestoreSameSharedPoolAdaptor) GetRestoreInRunning(ctx context.Context, restore *entity.LightweightRestore) (*GetLightweightRestoreOutput, error) {
	switch restore.GetLastAction() {
	case "", schema.LightweightRestoreActionScaleTiKV, schema.LightweightRestoreActionCreateTiBR:
		return &GetLightweightRestoreOutput{
			Status:  apiv1.LightweightRestoreStatus_PROVISIONING,
			Message: "provisioning resource for restore, current action is " + restore.GetLastAction(),
		}, nil
	case schema.LightweightRestoreActionExecRestore:
		return a.getDataRestoringOutput(ctx, restore)
	case 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
	}
}

func (a *lwRestoreSameSharedPoolAdaptor) 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())
	}
	// if it's in kernel task, return data_restoring with the progress
	resp, err := a.clustergatewayCli.GetLightweightRestore(ctx, targetSharedPool,
		tioperator.TiBRCRNameOfRestore(restore.RestoreID),
		&clustergateway.KernelLightweightRestoreIdentity{
			RestoreId:   restore.RestoreID,
			PDClusterId: restore.TargetPdClusterID,
			KeySpace:    restore.TargetKeyspace,
		})
	if err != nil {
		if errors.Is(err, clustergateway.RestoreNotFoundErr) {
			return &GetLightweightRestoreOutput{
				Status:  apiv1.LightweightRestoreStatus_DATA_RESTORING,
				Message: "wait for the kernel restore task created",
			}, nil
		}
		tilog.Error(ctx, "get kernel restore task failed",
			zap.Error(err), zap.String("restoreId", restore.RestoreID), zap.String("targetSharedPool", restore.TargetSharedPoolID))
		return nil, fmt.Errorf("get kernel restore task failed: %s", err.Error())
	}

	return calDataRestoringStatus4SameSharedPool(resp), nil
}

func calDataRestoringStatus4SameSharedPool(kernelTask *clustergateway.KernelLightweightRestore) *GetLightweightRestoreOutput {
	ouput := &GetLightweightRestoreOutput{
		DataRestoreDurationSeconds:    kernelTask.DurationSeconds,
		DataRestoreBytes:              kernelTask.RestoreBytes,
		DataRestoreStep:               kernelTask.Progress.Step,
		DataRestoreProgressPercentage: kernelTask.Progress.ProgressPercentage,
	}

	if kernelTask.ConflictRestoreId != 0 {
		ouput.Status = apiv1.LightweightRestoreStatus_DATA_RESTORING_ERROR
		ouput.Message = fmt.Sprintf("restore is conflict with another restore(%d)", kernelTask.ConflictRestoreId)
		ouput.Failure = kernelRestoreFailure("KERNEL_RESTORE_CONFLICT", ouput.Message)
		return ouput
	}

	if kernelTask.Status == clustergateway.LightweightRestoreStatus_Error {
		ouput.Status = apiv1.LightweightRestoreStatus_DATA_RESTORING_ERROR
		ouput.Message = fmt.Sprintf("data restoring is error: %s", kernelTask.Error)
		ouput.Failure = kernelRestoreFailure("KERNEL_TASK_FAILED", ouput.Message)
		return ouput
	}

	if kernelTask.Status == clustergateway.LightweightRestoreStatus_Succeed {
		ouput.Status = apiv1.LightweightRestoreStatus_DATA_RESTORING
		ouput.Message = "data restoring is succeed, restarting tidb for the target cluster"
		ouput.DataRestoreProgressPercentage = 100
		return ouput
	}

	ouput.Status = apiv1.LightweightRestoreStatus_DATA_RESTORING
	ouput.Message = "data is restoring"
	return ouput
}

func (a *lwRestoreSameSharedPoolAdaptor) GetPostActions(ctx context.Context, restore *entity.LightweightRestore) (*GetLightweightRestorePostActionsOutput, error) {
	// no actions currently
	return &GetLightweightRestorePostActionsOutput{
		GetLightweightRestorePostActionsResponse: &apiv1.GetLightweightRestorePostActionsResponse{},
	}, nil
}
