package server

import (
	"context"
	"errors"
	"fmt"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	braction "github.com/tidbcloud/cluster-service-ng/domain/workflow/action/br"
	"github.com/tidbcloud/cluster-service-ng/pkg/clustergateway"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type GetManualBackupRestoreInput struct {
	*apiv1.GetManualBackupRestoreRequest
}

type GetManualBackupRestoreOutput struct {
	*apiv1.ManualBackupRestore
}

func (s *serverImpl) GetManualBackupRestore(ctx context.Context, input *GetManualBackupRestoreInput) (*GetManualBackupRestoreOutput, error) {
	restore, err := s.manualBackupRestoreRepo.Load(ctx, input.RestoreId, false)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, status.Errorf(codes.NotFound, "manual backup restore not found")
		}
		tilog.Error(ctx, "failed to load manual backup restore record", zap.Error(err), zap.String("restoreId", input.RestoreId))
		return nil, fmt.Errorf("failed to load manual backup restore record: %w", err)
	}

	if restore.SourceClusterID != input.SourceClusterId || restore.SourceBackupID != input.BackupId {
		tilog.Warn(ctx, "manual backup restore scope mismatch",
			zap.String("restoreId", input.RestoreId),
			zap.String("requestedSourceClusterId", input.SourceClusterId),
			zap.String("requestedBackupId", input.BackupId),
			zap.String("restoreSourceClusterId", restore.SourceClusterID),
			zap.String("restoreSourceBackupId", restore.SourceBackupID),
		)
		return nil, status.Errorf(codes.NotFound, "manual backup restore not found")
	}

	if restore.Status == schema.ManualBackupRestoreStatusRunning {
		resp, err := s.getManualBackupRestoreInRunning(ctx, restore)
		if err != nil {
			return nil, err
		}
		return &GetManualBackupRestoreOutput{ManualBackupRestore: resp}, nil
	}

	resp := buildManualBackupRestoreOutput(restore)
	return &GetManualBackupRestoreOutput{ManualBackupRestore: resp}, nil
}

func (s *serverImpl) getManualBackupRestoreInRunning(ctx context.Context, restore *entity.ManualBackupRestore) (*apiv1.ManualBackupRestore, error) {
	out := buildManualBackupRestoreOutput(restore)
	targetSharedPool, err := s.sharedPoolRepo.Load(ctx, restore.TargetSharedPoolID, false)
	if err != nil {
		return nil, fmt.Errorf("failed to load target shared pool: %w", err)
	}

	task, err := s.clustergatewayCli.GetTiBRTask(ctx, targetSharedPool,
		tioperator.TiBRCRNameOfManualRestore(restore.RestoreID), braction.KernelRestorePackedTaskId)
	if err != nil {
		if errors.Is(err, clustergateway.ErrBRTaskNotFound) {
			return out, nil
		}
		return nil, fmt.Errorf("failed to get manual backup restore task in kernel: %w", err)
	}
	if task.State == clustergateway.TaskStateError {
		message := fmt.Sprintf("restore from manual backup task error in kernel, message: %s", task.Error)
		out.Status = apiv1.ManualBackupRestore_FAILED
		out.Message = message
		out.Failure = restoreFailureOutput(entity.RestoreFailureSourceKernel, "KERNEL_TASK_FAILED", message)
	}
	return out, nil
}

func buildManualBackupRestoreOutput(restore *entity.ManualBackupRestore) *apiv1.ManualBackupRestore {
	out := &apiv1.ManualBackupRestore{
		RestoreSize: restore.RestoreSize,
	}
	switch restore.Status {
	case schema.ManualBackupRestoreStatusRunning:
		out.Status = apiv1.ManualBackupRestore_RUNNING
	case schema.ManualBackupRestoreStatusSucceeded:
		out.Status = apiv1.ManualBackupRestore_SUCCEED
	case schema.ManualBackupRestoreStatusFailed:
		out.Status = apiv1.ManualBackupRestore_FAILED
		out.Failure = restoreFailureOutput(restore.FailureSource, restore.FailureCode, restore.FailureMessage)
	case schema.ManualBackupRestoreStatusCancelled:
		out.Status = apiv1.ManualBackupRestore_CANCELLED
	}
	return out
}
