package service

import (
	"context"
	"time"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
)

type backupResolutionLevel uint8

const (
	backupRestorePoint backupResolutionLevel = iota
	backupPhysical
)

// resolvedBackup is the result of resolving a backup ID to either a persisted
// backup, a virtual restore point, or a fully resolved virtual physical backup.
type resolvedBackup struct {
	Backup  *entity.UserBackup
	Virtual *VirtualBackup
	History *entity.BackupScheduleHistory
}

// hasVirtualRestorePoint reports whether the resolution result represents a
// virtual backup restore point. This is true both at the restore-point level
// (where Backup is nil) and at the physical level (where Backup has been
// resolved from the cluster proxy).
func (r *resolvedBackup) hasVirtualRestorePoint() bool {
	return r != nil && r.Virtual != nil
}

// isPersisted reports whether the resolution result is a purely persisted
// backup with no virtual component.
func (r *resolvedBackup) isPersisted() bool {
	return r != nil && r.Backup != nil && r.Virtual == nil
}

// resolveBackup resolves a backup ID to a resolvedBackup. For legacy IDs it
// loads the persisted backup; for virtual IDs it locates the matching schedule
// history and, at the backupPhysical level, resolves the real backup from the
// cluster proxy.
func (b *backupRestoreService) resolveBackup(
	ctx context.Context,
	clusterID, backupID string,
	level backupResolutionLevel,
) (*resolvedBackup, bizerr.BizError) {
	return b.resolveBackupInternal(ctx, clusterID, backupID, level)
}

func (b *backupRestoreService) resolveBackupForDelete(
	ctx context.Context,
	clusterID, backupID string,
) (*resolvedBackup, bizerr.BizError) {
	return b.resolveBackupInternal(ctx, clusterID, backupID, backupRestorePoint)
}

func (b *backupRestoreService) resolveBackupInternal(
	ctx context.Context,
	clusterID, backupID string,
	level backupResolutionLevel,
) (*resolvedBackup, bizerr.BizError) {
	ref, virtual, malformed := parseVirtualBackupID(backupID)
	if malformed {
		return nil, bizerr.New(bizerr.ErrCodeParams, "invalid virtual backup id")
	}
	if !virtual {
		var backup *entity.UserBackup
		var err bizerr.BizError
		if clusterID == "" {
			backup, err = b.backupRepo.GetByBackupId(ctx, backupID)
		} else {
			backup, err = b.backupRepo.GetByClusterIdAndBackupId(ctx, clusterID, backupID)
		}
		if err != nil {
			return nil, err
		}
		if backup.ScheduleTime != nil {
			deleted, tombstoneErr := b.backupTombstoneRepo.Exists(ctx, backup.ClusterID, *backup.ScheduleTime)
			if tombstoneErr != nil {
				return nil, tombstoneErr
			}
			if deleted {
				return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "")
			}
		}
		return &resolvedBackup{Backup: backup}, nil
	}
	virtualBackup, history, err := b.getVirtualBackup(ctx, clusterID, backupID, ref, time.Now())
	if err != nil {
		return nil, err
	}
	result := &resolvedBackup{Virtual: virtualBackup, History: history}
	if level == backupRestorePoint {
		return result, nil
	}
	result.Backup, err = b.resolveVirtualBackup(ctx, virtualBackup, history)
	if err != nil {
		return nil, err
	}
	return result, nil
}
