package clustergateway

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/go-resty/resty/v2"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
)

const QueryPiTr = "for-pitr"

type GetLightweightBackupRequest struct {
	Query       string
	PointInTime string
	UseS3Bucket string
	UsePrefix   string
	UseEndpoint string
}

type ListLightweightBackupsRequest struct {
	FromPrefix  string
	MaxCount    uint32
	LoadDetails bool
}

type ListLightweightBackupsResponse struct {
	Data      []KernelLightweightBackupItem `json:"data"`
	ClusterID uint64                        `json:"cluster_id"`
	HasMore   bool                          `json:"has_more"`
}

// BackupStatus is supplied by cloud-storage-engine. Archive progress is
// day-granular, hence LastArchivedDate deliberately remains a string in the
// transport type and is parsed explicitly by its consumer.
type BackupStatus struct {
	LastArchivedDate    *string    `json:"last_archived_date"`
	FirstAutoBackupTime *time.Time `json:"first_auto_backup_time"`
	LastAutoBackupTime  *time.Time `json:"last_auto_backup_time"`
}

type BackupStatusHTTPError struct{ StatusCode int }

func (e *BackupStatusHTTPError) Error() string {
	return fmt.Sprintf("GetBackupStatus returned HTTP status %d", e.StatusCode)
}

type BackupStatusDecodeError struct{ Err error }

func (e *BackupStatusDecodeError) Error() string {
	return "decode GetBackupStatus response: " + e.Err.Error()
}
func (e *BackupStatusDecodeError) Unwrap() error { return e.Err }

type KernelLightweightBackupItem struct {
	ID             uint64            `json:"v1_compat_id"`
	Name           string            `json:"name"`
	LastModifyTime string            `json:"last_modify_time"`
	Details        BackupItemDetails `json:"details"`
}

type BackupItemDetails struct {
	BackupTs  uint64               `json:"backup_ts"`
	SafeTs    uint64               `json:"safe_ts"`
	Keyspaces []KeyspaceBackupInfo `json:"keyspaces"`
}

type KeyspaceBackupInfo struct {
	KeyspaceId    uint64 `json:"keyspace_id"`
	KeyspaceName  string `json:"keyspace_name"`
	KeyspaceState string `json:"keyspace_state"`
	Size          uint64 `json:"size"`
}

func (t KeyspaceBackupInfo) IsNormalKeyspace() bool {
	return (t.KeyspaceState == "ENABLED" || t.KeyspaceState == "UNKNOWN_BECAUSE_LOADED_FROM_BACKUP") && t.KeyspaceName != "DEFAULT" && t.KeyspaceName != "SYSTEM"
}

type CreateLightweightBackupResponse struct {
	BackupName string `json:"backup_name"`
}

type CreateLightweightRestoreRequest struct {
	RestoreId      string
	PDClusterId    string
	KeySpace       string
	SourceKeySpace string
	BackupId       string
	BackupName     string
	PointInTime    string
	MaxThroughput  string // optional
}

type KernelLightweightRestore struct {
	RestoreID uint64 `json:"id"`
	Keyspace  string `json:"keyspace"`
	// not empty if there has conflict
	ConflictRestoreId uint64 `json:"conflict_restore_id"`

	// following fields are only for normal restore
	RestoreType     LightweightRestoreType     `json:"restore_type"`
	Status          LightweightRestoreStatus   `json:"status"`
	Error           string                     `json:"error"`
	DurationSeconds uint64                     `json:"duration"`
	RestoreBytes    uint64                     `json:"restore_bytes"`
	Progress        LightweightRestoreProgress `json:"progress"`
}

type LightweightRestoreType string

const (
	LightweightRestoreType_Normal LightweightRestoreType = "Normal"
	LightweightRestoreType_Pitr   LightweightRestoreType = "Pitr"
)

type LightweightRestoreStatus string

const (
	LightweightRestoreStatus_Pending LightweightRestoreStatus = "Pending"
	LightweightRestoreStatus_Init    LightweightRestoreStatus = "Init"
	LightweightRestoreStatus_Running LightweightRestoreStatus = "Running"
	LightweightRestoreStatus_Succeed LightweightRestoreStatus = "Succeed"
	LightweightRestoreStatus_Error   LightweightRestoreStatus = "Error"
)

type LightweightRestoreProgress struct {
	Step               string `json:"step"`
	ProgressPercentage uint32 `json:"progress"`
}

type KernelLightweightRestoreIdentity struct {
	RestoreId   string
	PDClusterId string
	KeySpace    string
}

// currently only for get the bacup that used to restore pitr in a different sharedpool
func (c *clusterGatewayImpl) GetLightweightBackup(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *GetLightweightBackupRequest) (*KernelLightweightBackupItem, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodGet, operationMap["GetLightweightBackup"], func(request *resty.Request) *resty.Request {
		request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetQueryParam(backupQueryParameter, req.Query).
			SetQueryParam(pointInTimeQueryParameter, req.PointInTime)
		if req.UseS3Bucket != "" {
			request.SetQueryParam(useS3BucketQueryParameter, req.UseS3Bucket)
		}
		if req.UsePrefix != "" {
			request.SetQueryParam(usePrefixQueryParameter, req.UsePrefix)
		}
		if req.UseEndpoint != "" {
			request.SetQueryParam(useEndpointQueryParameter, req.UseEndpoint)
		}
		return request
	})
	if err != nil {
		return nil, fmt.Errorf("call GetLightweightBackup failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("GetLightweightBackup response not expected: %s", resp.String())
	}

	var out KernelLightweightBackupItem
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal GetLightweightBackup response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) ListLightweightBackups(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *ListLightweightBackupsRequest) (*ListLightweightBackupsResponse, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodGet, operationMap["ListLightweightBackups"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetQueryParam(fromPrefixParameter, req.FromPrefix).
			SetQueryParam(loadDetailsParameter, strconv.FormatBool(req.LoadDetails)).
			SetQueryParam(maxCountQueryParameter, fmt.Sprintf("%d", req.MaxCount))
	})
	if err != nil {
		return nil, fmt.Errorf("call ListLightweightBackups failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("ListLightweightBackups response not expected: %s", resp.String())
	}

	var out ListLightweightBackupsResponse
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal ListLightweightBackups response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) CreateLightweightBackup(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string) (*CreateLightweightBackupResponse, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodPost, operationMap["CreateLightweightBackup"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName)
	})
	if err != nil {
		return nil, fmt.Errorf("call CreateLightweightBackup failed: %w", err)
	}

	if resp.StatusCode() != http.StatusOK &&
		resp.StatusCode() != http.StatusCreated &&
		resp.StatusCode() != http.StatusAccepted {
		return nil, fmt.Errorf("CreateLightweightBackup response not expected: %s", resp.String())
	}

	var out CreateLightweightBackupResponse
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal CreateLightweightBackup response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) CreateLightweightRestore(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *CreateLightweightRestoreRequest) (*KernelLightweightRestore, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodPut, operationMap["CreateLightweightRestore"], func(request *resty.Request) *resty.Request {
		request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(restoreIdParameter, req.RestoreId).
			SetQueryParam(pdClusterIdQueryParameter, req.PDClusterId).
			SetQueryParam(keySpaceQueryParameter, req.KeySpace).
			SetQueryParam(sourceKeySpaceQueryParameter, req.SourceKeySpace)
		if req.BackupId != "" {
			request.SetQueryParam(backupIdQueryParameter, req.BackupId)
		}
		if strings.TrimSpace(req.BackupName) != "" {
			request.SetQueryParam(backupNameQueryParameter, formatBackupNameIfNeeded(req.BackupName))
		}
		if strings.TrimSpace(req.PointInTime) != "" {
			request.SetQueryParam(pointInTimeQueryParameter, strings.TrimSpace(req.PointInTime))
		}
		if strings.TrimSpace(req.MaxThroughput) != "" {
			request.SetQueryParam(maxThroughputQueryParameter, req.MaxThroughput)
		}
		return request
	})
	if err != nil {
		return nil, fmt.Errorf("call CreateLightweightRestore failed: %w", err)
	}

	if resp.StatusCode() != http.StatusOK &&
		resp.StatusCode() != http.StatusCreated &&
		resp.StatusCode() != http.StatusAccepted &&
		resp.StatusCode() != http.StatusConflict {
		return nil, fmt.Errorf("CreateLightweightRestore response not expected: %s", resp.String())
	}

	var out KernelLightweightRestore
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal CreateLightweightRestore response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func formatBackupNameIfNeeded(backupName string) string {
	backupName = strings.TrimSpace(backupName)
	suffix := ".meta"
	if strings.HasSuffix(backupName, suffix) {
		cleaned := strings.TrimSuffix(backupName, ".meta")
		return strings.ReplaceAll(cleaned, "/", "")
	}
	return backupName
}

var RestoreNotFoundErr = fmt.Errorf("restore not found")

func (c *clusterGatewayImpl) GetLightweightRestore(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *KernelLightweightRestoreIdentity) (*KernelLightweightRestore, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodGet, operationMap["GetLightweightRestore"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(restoreIdParameter, req.RestoreId).
			SetQueryParam(pdClusterIdQueryParameter, req.PDClusterId).
			SetQueryParam(keySpaceQueryParameter, req.KeySpace)
	})
	if err != nil {
		return nil, fmt.Errorf("call GetLightweightRestore failed: %w", err)
	}

	if resp.StatusCode() == http.StatusNotFound {
		return nil, RestoreNotFoundErr
	}

	if resp.StatusCode() != http.StatusOK && resp.StatusCode() != http.StatusAccepted {
		return nil, fmt.Errorf("GetLightweightRestore response not expected: %s", resp.String())
	}

	var out KernelLightweightRestore
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal GetLightweightRestore response failed: %w", err)
	}

	return &out, nil
}

func (c *clusterGatewayImpl) DeleteLightweightRestore(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *KernelLightweightRestoreIdentity) error {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodDelete, operationMap["DeleteLightweightRestore"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(restoreIdParameter, req.RestoreId).
			SetQueryParam(pdClusterIdQueryParameter, req.PDClusterId).
			SetQueryParam(keySpaceQueryParameter, req.KeySpace)
	})
	if err != nil {
		return fmt.Errorf("call DeleteLightweightRestore failed: %w", err)
	}

	if resp.StatusCode() != http.StatusOK && resp.StatusCode() != http.StatusNotFound && resp.StatusCode() != http.StatusFound {
		return fmt.Errorf("DeleteLightweightRestore response not expected: %s", resp.String())
	}

	return nil
}

func (c *clusterGatewayImpl) FlushWAL(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string) error {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodPost, operationMap["FlushWAL"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName)
	})
	if err != nil {
		return fmt.Errorf("call FlushWAL failed: %w", err)
	}

	if resp.StatusCode() == http.StatusNotFound {
		return ErrFlushWALUnsupported
	}
	if resp.StatusCode() == http.StatusInternalServerError {
		if strings.Contains(resp.String(), "Not Found") {
			// for old version of TiBR, it doesn't support flush wal
			return ErrFlushWALUnsupported
		}
	}

	if resp.StatusCode() != http.StatusOK {
		return &FlushWALHTTPError{StatusCode: resp.StatusCode()}
	}

	return nil
}

// Pack & Restore Packed Backup API Types

// CreatePackBackupTaskRequest represents the request to create a pack backup task
type CreatePackBackupTaskRequest struct {
	TaskId      string
	BackupName  string
	Keyspace    string
	UseS3Bucket string // optional
	UsePrefix   string // optional
	UseEndpoint string // optional, Azure blob account endpoint
}

// CreateCopyPackedBackupTaskRequest represents the request to create a copy packed backup task
type CreateCopyPackedBackupTaskRequest struct {
	TaskId        string
	ExoticBackup  string
	CopyTo        string
	UseS3Bucket   string // optional
	UsePrefix     string // optional
	MaxThroughput string // optional
}

// CreateRestorePackedBackupTaskRequest represents the request to create a restore packed backup task
type CreateRestorePackedBackupTaskRequest struct {
	TaskId               string
	ClusterId            string // pd cluster id
	ExoticBackup         string
	Keyspace             string
	PointInTime          string // optional, RFC3339 format
	ExoticBackupEndpoint string // optional, Azure blob account endpoint
	MaxThroughput        string // optional
}

// TaskRequest represents the base request structure for task operations
type TaskRequest struct {
	Type string `json:"type"`
	Id   uint64 `json:"id"`
}

// PackBackupRequest represents the pack backup task request
type PackBackupRequest struct {
	TaskRequest
	BackupName  string `json:"backup_name"`
	Keyspace    string `json:"keyspace"`
	UseS3Bucket string `json:"use_s3_bucket,omitempty"`
	UsePrefix   string `json:"use_prefix,omitempty"`
	UseEndpoint string `json:"use_endpoint,omitempty"`
}

// CopyPackedRequest represents the copy packed backup task request
type CopyPackedRequest struct {
	TaskRequest
	ExoticBackup string `json:"exotic_backup"`
	CopyTo       string `json:"copy_to"`
	UseS3Bucket  string `json:"use_s3_bucket,omitempty"`
	UsePrefix    string `json:"use_prefix,omitempty"`
}

// RestorePackedRequest represents the restore packed backup task request
type RestorePackedRequest struct {
	TaskRequest
	ClusterId            uint64 `json:"cluster_id"` // pd cluster id
	ExoticBackup         string `json:"exotic_backup"`
	Keyspace             string `json:"keyspace"`
	PointInTime          string `json:"point_in_time,omitempty"`
	ExoticBackupEndpoint string `json:"exotic_backup_endpoint,omitempty"`
}

// TaskState represents the state of a task
type TaskState string

const (
	TaskStatePending TaskState = "Pending"
	TaskStateInit    TaskState = "Init"
	TaskStateRunning TaskState = "Running"
	TaskStateDone    TaskState = "Done"
	TaskStateError   TaskState = "Error"
)

// TaskStateTransition represents a state transition in task history
type TaskStateTransition struct {
	To string `json:"to"`
	At string `json:"at"`
}

// TaskResponse represents the response structure for all task operations
type TaskResponse struct {
	Id             uint64                `json:"id"`
	Request        any                   `json:"request"`
	CreateTime     string                `json:"create_time"`
	State          TaskState             `json:"state"`
	LastUpdateTime string                `json:"last_update_time,omitempty"`
	Status         string                `json:"status,omitempty"`
	Info           map[string]any        `json:"info,omitempty"`
	Error          string                `json:"error,omitempty"`
	StateTrans     []TaskStateTransition `json:"state_trans"`
}

// PackedBackupView represents the info for a completed pack backup task
type PackedBackupView struct {
	ClusterId    uint64 `json:"cluster_id"`
	BackupTs     uint64 `json:"backup_ts"`
	SafeTs       uint64 `json:"safe_ts"`
	KeyspaceName string `json:"keyspace_name"`
	ResolvedTs   uint64 `json:"resolved_ts"`
	EngineSize   uint64 `json:"engine_size"`
	KeyspaceSize uint64 `json:"keyspace_size"`
	Path         string `json:"path"`
}

// CopyPackedView represents the info for a completed copy packed backup task
type CopyPackedView struct {
	NewExoticPath string `json:"new_exotic_path"`
	FilesCopied   uint64 `json:"files_copied"`
}

// RestorePackedView represents the info for a completed restore packed backup task
type RestorePackedView struct {
	OriginClusterId    uint64 `json:"origin_cluster_id"`
	OriginKeyspaceName string `json:"origin_keyspace_name"`
	TargetClusterId    uint64 `json:"target_cluster_id"`
	TargetKeyspaceName string `json:"target_keyspace_name"`
	PointInTime        string `json:"point_in_time"`
	RestoreBytes       uint64 `json:"restore_bytes"`
	ToleratedErr       uint64 `json:"tolerated_err"`
}

// ExoticBackupInfo represents the response from the exotic backup query API
type ExoticBackupInfo struct {
	KeyspaceName string `json:"keyspace_name"`
	BackupTime   string `json:"backup_time"`
	BackupSize   uint64 `json:"backup_size"`
	KeyspaceSize uint64 `json:"keyspace_size"`
	BackupTs     uint64 `json:"backup_ts"`
	ClusterId    uint64 `json:"cluster_id"`
	SafeTs       uint64 `json:"safe_ts"`
	ResolvedTs   uint64 `json:"resolved_ts"`
	EngineSize   uint64 `json:"engine_size"`
	Path         string `json:"path"`
}

// Pack & Restore Packed Backup API Implementation

func (c *clusterGatewayImpl) CreatePackBackupTask(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *CreatePackBackupTaskRequest) (*TaskResponse, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodPut, operationMap["CreatePackBackupTask"], func(request *resty.Request) *resty.Request {
		request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(taskIdParameter, req.TaskId).
			SetQueryParam(backupNameTaskQueryParameter, req.BackupName).
			SetQueryParam(keyspaceTaskQueryParameter, req.Keyspace)

		if strings.TrimSpace(req.UseS3Bucket) != "" {
			request.SetQueryParam(useS3BucketQueryParameter, req.UseS3Bucket)
		}
		if strings.TrimSpace(req.UsePrefix) != "" {
			request.SetQueryParam(usePrefixQueryParameter, req.UsePrefix)
		}
		if strings.TrimSpace(req.UseEndpoint) != "" {
			request.SetQueryParam(useEndpointQueryParameter, req.UseEndpoint)
		}
		return request
	})
	if err != nil {
		return nil, fmt.Errorf("call CreatePackBackupTask failed: %w", err)
	}

	if resp.StatusCode() != http.StatusOK &&
		resp.StatusCode() != http.StatusCreated &&
		resp.StatusCode() != http.StatusAccepted {
		return nil, fmt.Errorf("CreatePackBackupTask response not expected, status: %d, body: %s", resp.StatusCode(), resp.String())
	}

	var out TaskResponse
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal CreatePackBackupTask response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) CreateCopyPackedBackupTask(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *CreateCopyPackedBackupTaskRequest) (*TaskResponse, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodPut, operationMap["CreateCopyPackedBackupTask"], func(request *resty.Request) *resty.Request {
		request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(taskIdParameter, req.TaskId).
			SetQueryParam(exoticBackupQueryParameter, req.ExoticBackup).
			SetQueryParam(copyToQueryParameter, req.CopyTo)

		if strings.TrimSpace(req.UseS3Bucket) != "" {
			request.SetQueryParam(useS3BucketQueryParameter, req.UseS3Bucket)
		}
		if strings.TrimSpace(req.UsePrefix) != "" {
			request.SetQueryParam(usePrefixQueryParameter, req.UsePrefix)
		}
		if strings.TrimSpace(req.MaxThroughput) != "" {
			request.SetQueryParam(maxThroughputQueryParameter, req.MaxThroughput)
		}
		return request
	})
	if err != nil {
		return nil, fmt.Errorf("call CreateCopyPackedBackupTask failed: %w", err)
	}

	if resp.StatusCode() != http.StatusOK &&
		resp.StatusCode() != http.StatusAccepted &&
		resp.StatusCode() != http.StatusCreated {
		return nil, fmt.Errorf("CreateCopyPackedBackupTask response not expected, status: %d, body: %s", resp.StatusCode(), resp.String())
	}

	var out TaskResponse
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal CreateCopyPackedBackupTask response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) CreateRestorePackedBackupTask(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string, req *CreateRestorePackedBackupTaskRequest) (*TaskResponse, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodPut, operationMap["CreateRestorePackedBackupTask"], func(request *resty.Request) *resty.Request {
		request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(taskIdParameter, req.TaskId).
			SetQueryParam(clusterIdTaskQueryParameter, req.ClusterId).
			SetQueryParam(exoticBackupQueryParameter, req.ExoticBackup).
			SetQueryParam(keyspaceTaskQueryParameter, req.Keyspace)

		if strings.TrimSpace(req.PointInTime) != "" {
			request.SetQueryParam(pointInTimeQueryParameter, req.PointInTime)
		}
		if strings.TrimSpace(req.ExoticBackupEndpoint) != "" {
			request.SetQueryParam(exoticBackupEndpointParameter, req.ExoticBackupEndpoint)
		}
		if strings.TrimSpace(req.MaxThroughput) != "" {
			request.SetQueryParam(maxThroughputQueryParameter, req.MaxThroughput)
		}
		return request
	})
	if err != nil {
		return nil, fmt.Errorf("call CreateRestorePackedBackupTask failed: %w", err)
	}

	if resp.StatusCode() != http.StatusOK &&
		resp.StatusCode() != http.StatusCreated &&
		resp.StatusCode() != http.StatusAccepted {
		return nil, fmt.Errorf("CreateRestorePackedBackupTask response not expected, status: %d, body: %s", resp.StatusCode(), resp.String())
	}

	var out TaskResponse
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal CreateRestorePackedBackupTask response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

var ErrBRTaskNotFound = fmt.Errorf("task not found")

func (c *clusterGatewayImpl) GetTiBRTask(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName, taskId string) (*TaskResponse, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodGet, operationMap["GetTask"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(taskIdParameter, taskId)
	})
	if err != nil {
		return nil, fmt.Errorf("call GetTask failed: %w", err)
	}

	if resp.StatusCode() == http.StatusNotFound {
		return nil, ErrBRTaskNotFound
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("GetTask response not expected: %s", resp.String())
	}

	var out TaskResponse
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal GetTask response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) DeleteTiBRTask(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName, taskId string) (*TaskResponse, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodDelete, operationMap["DeleteTask"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetPathParam(taskIdParameter, taskId)
	})
	if err != nil {
		return nil, fmt.Errorf("call DeleteTask failed: %w", err)
	}

	if resp.StatusCode() == http.StatusNotFound {
		return nil, ErrBRTaskNotFound
	}

	if resp.StatusCode() == http.StatusConflict {
		return nil, fmt.Errorf("task is still running")
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("DeleteTask response not expected: %s", resp.String())
	}

	var out TaskResponse
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal DeleteTask response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) GetExoticBackupInfo(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName, exoticBackup string) (*ExoticBackupInfo, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodGet, operationMap["GetExoticBackupInfo"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName).
			SetQueryParam(exoticBackupQueryParameter, exoticBackup)
	})
	if err != nil {
		return nil, fmt.Errorf("call GetExoticBackupInfo failed: %w", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("GetExoticBackupInfo response not expected, status: %d, body: %s", resp.StatusCode(), resp.String())
	}

	var out ExoticBackupInfo
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal GetExoticBackupInfo response failed: %w, body: %s", err, string(resp.Body()))
	}

	return &out, nil
}

func (c *clusterGatewayImpl) GetBackupStatus(ctx context.Context, sharedPool *entity.SharedPool, tibrCRName string) (*BackupStatus, error) {
	resp, err := c.callTiBR(ctx, sharedPool, http.MethodGet, operationMap["GetBackupStatus"], func(request *resty.Request) *resty.Request {
		return request.SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(tibrCRNameParameter, tibrCRName)
	})
	if err != nil {
		return nil, fmt.Errorf("call GetBackupStatus failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, &BackupStatusHTTPError{StatusCode: resp.StatusCode()}
	}

	var out BackupStatus
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, &BackupStatusDecodeError{Err: err}
	}
	return &out, nil
}
