# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Backup and Restore Module - Phase 4

Comprehensive backup system for disaster recovery.

Features:
- Full system backup (config, data, users, audit)
- Incremental backups
- Encrypted backup archives
- Restore with validation
- Scheduled backups
- S3/cloud storage support

Backup includes:
- Configuration files
- User database
- Conversation history
- Analytics data
- Audit logs
- Skill data

Configuration:
    backup:
      enabled: true
      schedule: daily
      retention_days: 30
      encrypt: true
      storage: local  # or s3
      path: /var/backups/familiar
"""

from __future__ import annotations

import hashlib
import json
import logging
import os
import shutil
import tarfile
import tempfile
from datetime import datetime, timedelta, timezone

_UTC = timezone.utc
from dataclasses import asdict, dataclass  # noqa: E402
from enum import Enum  # noqa: E402
from pathlib import Path  # noqa: E402
from typing import Any, Dict, List, Optional  # noqa: E402

logger = logging.getLogger(__name__)

# Optional encryption
try:
    from cryptography.fernet import Fernet

    HAS_ENCRYPTION = True
except ImportError:
    HAS_ENCRYPTION = False


class BackupType(str, Enum):
    """Type of backup."""

    FULL = "full"
    INCREMENTAL = "incremental"
    CONFIG_ONLY = "config_only"
    DATA_ONLY = "data_only"


class BackupStatus(str, Enum):
    """Backup operation status."""

    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    COMPLETED = "completed"
    FAILED = "failed"
    VERIFIED = "verified"


@dataclass
class BackupManifest:
    """Manifest describing a backup."""

    id: str
    created_at: datetime
    backup_type: BackupType
    status: BackupStatus

    # Contents
    files: List[str]
    total_size: int
    file_count: int

    # Checksums
    checksum: str  # SHA256 of archive (post-encryption if encrypted)
    file_checksums: Dict[str, str]

    # Metadata
    version: str

    # Optional checksums
    checksum_pre_encryption: str = ""  # SHA256 before encryption (for verify)
    node_id: str = None
    encrypted: bool = False
    compressed: bool = True

    # Timing
    duration_seconds: float = 0

    # Errors
    errors: List[str] = None

    def to_dict(self) -> Dict:
        d = asdict(self)
        d["created_at"] = self.created_at.isoformat()
        d["backup_type"] = self.backup_type.value
        d["status"] = self.status.value
        return d

    @classmethod
    def from_dict(cls, d: Dict) -> "BackupManifest":
        d = d.copy()
        d["created_at"] = datetime.fromisoformat(d["created_at"])
        d["backup_type"] = BackupType(d["backup_type"])
        d["status"] = BackupStatus(d["status"])
        return cls(**d)


@dataclass
class BackupConfig:
    """Backup configuration."""

    enabled: bool = True

    # Schedule
    schedule: str = "daily"  # hourly, daily, weekly
    retention_days: int = 30

    # Storage
    storage: str = "local"  # local, s3
    path: Path = None
    s3_bucket: str = None
    s3_prefix: str = "backups/"

    # Security
    encrypt: bool = False
    encryption_key: str = None  # Fernet key

    # Options
    compress: bool = True
    include_audit: bool = True
    include_history: bool = True

    def __post_init__(self):
        if self.path is None:
            self.path = Path.home() / ".familiar" / "backups"

    @classmethod
    def from_dict(cls, d: Dict) -> "BackupConfig":
        d = d.copy()
        if "path" in d and d["path"]:
            d["path"] = Path(d["path"])
        return cls(**d)


class BackupManager:
    """
    Manages backups and restores.

    Usage:
        manager = BackupManager()

        # Create backup
        manifest = manager.create_backup()

        # List backups
        backups = manager.list_backups()

        # Restore
        manager.restore(backup_id)
    """

    def __init__(self, config: BackupConfig = None):
        self.config = config or BackupConfig()
        self.config.path.mkdir(parents=True, exist_ok=True)

        # Encryption
        self._fernet: Optional[Fernet] = None
        if self.config.encrypt and self.config.encryption_key:
            if HAS_ENCRYPTION:
                self._fernet = Fernet(self.config.encryption_key.encode())
            else:
                logger.warning("cryptography not installed - encryption disabled")

    def create_backup(
        self,
        backup_type: BackupType = BackupType.FULL,
        description: str = None,
    ) -> BackupManifest:
        """
        Create a backup.

        Args:
            backup_type: Type of backup to create
            description: Optional description

        Returns:
            Manifest describing the backup
        """
        import time

        start_time = time.time()

        # Generate backup ID
        backup_id = datetime.now(_UTC).replace(tzinfo=None).strftime("%Y%m%d_%H%M%S")

        logger.info(f"Creating {backup_type.value} backup: {backup_id}")

        # Create manifest
        manifest = BackupManifest(
            id=backup_id,
            created_at=datetime.now(_UTC).replace(tzinfo=None),
            backup_type=backup_type,
            status=BackupStatus.IN_PROGRESS,
            files=[],
            total_size=0,
            file_count=0,
            checksum="",
            file_checksums={},
            version="2.0.0",
            encrypted=self.config.encrypt and self._fernet is not None,
            compressed=self.config.compress,
            errors=[],
        )

        try:
            # Create temp directory for backup contents
            with tempfile.TemporaryDirectory() as temp_dir:
                temp_path = Path(temp_dir)

                # Collect files based on backup type
                if backup_type == BackupType.FULL:
                    self._backup_config(temp_path, manifest)
                    self._backup_users(temp_path, manifest)
                    if self.config.include_history:
                        self._backup_history(temp_path, manifest)
                    self._backup_analytics(temp_path, manifest)
                    if self.config.include_audit:
                        self._backup_audit(temp_path, manifest)
                    self._backup_skills(temp_path, manifest)
                    # Episodic memory: per-user recalled conversation history.
                    # Highest-priority data for nonprofit/Echidna deployments.
                    self._backup_episodic(
                        temp_path, manifest, episodic_dir=getattr(self, "_episodic_dir", None)
                    )

                elif backup_type == BackupType.CONFIG_ONLY:
                    self._backup_config(temp_path, manifest)

                elif backup_type == BackupType.DATA_ONLY:
                    self._backup_users(temp_path, manifest)
                    if self.config.include_history:
                        self._backup_history(temp_path, manifest)
                    self._backup_analytics(temp_path, manifest)
                    self._backup_episodic(
                        temp_path, manifest, episodic_dir=getattr(self, "_episodic_dir", None)
                    )

                # Write manifest
                manifest_path = temp_path / "manifest.json"
                with open(manifest_path, "w") as f:
                    json.dump(manifest.to_dict(), f, indent=2)

                # Create archive
                archive_name = f"familiar_backup_{backup_id}"
                archive_path = self.config.path / f"{archive_name}.tar.gz"

                with tarfile.open(archive_path, "w:gz" if self.config.compress else "w") as tar:
                    for item in temp_path.iterdir():
                        tar.add(item, arcname=item.name)

                # Calculate archive checksum (pre-encryption)
                pre_enc_checksum = self._file_checksum(archive_path)
                manifest.checksum = pre_enc_checksum
                manifest.total_size = archive_path.stat().st_size

                # Encrypt if configured
                if self._fernet:
                    manifest.checksum_pre_encryption = pre_enc_checksum
                    self._encrypt_file(archive_path)
                    manifest.encrypted = True
                    # Store post-encryption checksum for file-level integrity
                    manifest.checksum = self._file_checksum(archive_path)

                # Update manifest with final info
                manifest.status = BackupStatus.COMPLETED
                manifest.duration_seconds = time.time() - start_time

                # Save manifest separately
                self._save_manifest(manifest)

                logger.info(
                    f"Backup completed: {backup_id} "
                    f"({manifest.file_count} files, {manifest.total_size} bytes, "
                    f"{manifest.duration_seconds:.1f}s)"
                )

                # Log to audit
                self._audit_backup(manifest, "create")

                return manifest

        except Exception as e:
            logger.error(f"Backup failed: {e}")
            manifest.status = BackupStatus.FAILED
            manifest.errors = manifest.errors or []
            manifest.errors.append(str(e))
            self._save_manifest(manifest)
            raise

    def _backup_config(self, dest: Path, manifest: BackupManifest):
        """Backup configuration files."""
        from .paths import CONFIG_FILE

        config_dir = dest / "config"
        config_dir.mkdir()

        # Main config
        if Path(CONFIG_FILE).exists():
            shutil.copy2(CONFIG_FILE, config_dir / "config.yaml")
            manifest.files.append("config/config.yaml")
            manifest.file_checksums["config/config.yaml"] = self._file_checksum(CONFIG_FILE)
            manifest.file_count += 1

    def _backup_users(self, dest: Path, manifest: BackupManifest):
        """Backup user database."""
        from .paths import DATA_DIR

        users_db = Path(DATA_DIR) / "users.db"
        if users_db.exists():
            data_dir = dest / "data"
            data_dir.mkdir(exist_ok=True)
            shutil.copy2(users_db, data_dir / "users.db")
            manifest.files.append("data/users.db")
            manifest.file_checksums["data/users.db"] = self._file_checksum(users_db)
            manifest.file_count += 1

    def _backup_history(self, dest: Path, manifest: BackupManifest):
        """Backup conversation history."""
        from .paths import DATA_DIR

        history_file = Path(DATA_DIR) / "history.json"
        if history_file.exists():
            data_dir = dest / "data"
            data_dir.mkdir(exist_ok=True)
            shutil.copy2(history_file, data_dir / "history.json")
            manifest.files.append("data/history.json")
            manifest.file_checksums["data/history.json"] = self._file_checksum(history_file)
            manifest.file_count += 1

        # User-specific history
        user_data = Path(DATA_DIR) / "users"
        if user_data.exists():
            for user_dir in user_data.iterdir():
                if user_dir.is_dir():
                    history = user_dir / "history.json"
                    if history.exists():
                        dest_dir = dest / "data" / "users" / user_dir.name
                        dest_dir.mkdir(parents=True, exist_ok=True)
                        shutil.copy2(history, dest_dir / "history.json")
                        rel_path = f"data/users/{user_dir.name}/history.json"
                        manifest.files.append(rel_path)
                        manifest.file_checksums[rel_path] = self._file_checksum(history)
                        manifest.file_count += 1

    def _backup_analytics(self, dest: Path, manifest: BackupManifest):
        """Backup analytics database."""
        from .paths import DATA_DIR

        analytics_db = Path(DATA_DIR) / "analytics.db"
        if analytics_db.exists():
            data_dir = dest / "data"
            data_dir.mkdir(exist_ok=True)
            shutil.copy2(analytics_db, data_dir / "analytics.db")
            manifest.files.append("data/analytics.db")
            manifest.file_checksums["data/analytics.db"] = self._file_checksum(analytics_db)
            manifest.file_count += 1

    def _backup_audit(self, dest: Path, manifest: BackupManifest):
        """Backup audit logs."""
        from .paths import DATA_DIR

        audit_db = Path(DATA_DIR) / "audit.db"
        if audit_db.exists():
            data_dir = dest / "data"
            data_dir.mkdir(exist_ok=True)
            shutil.copy2(audit_db, data_dir / "audit.db")
            manifest.files.append("data/audit.db")
            manifest.file_checksums["data/audit.db"] = self._file_checksum(audit_db)
            manifest.file_count += 1

    def _backup_skills(self, dest: Path, manifest: BackupManifest):
        """Backup skill data."""
        from .paths import SKILLS_DIR

        if Path(SKILLS_DIR).exists():
            skills_dir = dest / "skills"
            shutil.copytree(SKILLS_DIR, skills_dir, dirs_exist_ok=True)

            for root, dirs, files in os.walk(skills_dir):
                for file in files:
                    filepath = Path(root) / file
                    rel_path = str(filepath.relative_to(dest))
                    manifest.files.append(rel_path)
                    manifest.file_checksums[rel_path] = self._file_checksum(filepath)
                    manifest.file_count += 1

    def _backup_episodic(
        self, dest: Path, manifest: BackupManifest, episodic_dir: Optional[Path] = None
    ):
        """
        Backup per-user episodic memory files (*.json and *.json.enc).

        These are the highest-priority files for Echidna: each file holds the
        complete recalled conversation history for one user/donor.  A corrupt
        or missing file means permanent loss of that relationship context.

        Args:
            dest:         Temp staging directory (same temp_path as other backup methods)
            manifest:     Manifest to update
            episodic_dir: Override path; defaults to ~/.familiar/episodic
        """
        if episodic_dir is None:
            episodic_dir = Path.home() / ".familiar" / "episodic"

        if not episodic_dir.exists():
            return

        episodic_dest = dest / "episodic"
        episodic_dest.mkdir(parents=True, exist_ok=True)

        for ep_file in episodic_dir.iterdir():
            if ep_file.is_file() and (
                ep_file.suffix == ".json" or ep_file.name.endswith(".json.enc")
            ):
                shutil.copy2(ep_file, episodic_dest / ep_file.name)
                rel_path = f"episodic/{ep_file.name}"
                manifest.files.append(rel_path)
                manifest.file_checksums[rel_path] = self._file_checksum(ep_file)
                manifest.file_count += 1

    def _file_checksum(self, filepath: Path) -> str:
        """Calculate SHA256 checksum of file."""
        sha256 = hashlib.sha256()
        with open(filepath, "rb") as f:
            for chunk in iter(lambda: f.read(8192), b""):
                sha256.update(chunk)
        return sha256.hexdigest()

    def _encrypt_file(self, filepath: Path):
        """Encrypt a file in place."""
        if not self._fernet:
            return

        with open(filepath, "rb") as f:
            data = f.read()

        encrypted = self._fernet.encrypt(data)

        with open(filepath, "wb") as f:
            f.write(encrypted)

    def _decrypt_file(self, filepath: Path):
        """Decrypt a file in place."""
        if not self._fernet:
            return

        with open(filepath, "rb") as f:
            data = f.read()

        decrypted = self._fernet.decrypt(data)

        with open(filepath, "wb") as f:
            f.write(decrypted)

    def _save_manifest(self, manifest: BackupManifest):
        """Save manifest to disk."""
        manifest_file = self.config.path / f"manifest_{manifest.id}.json"
        with open(manifest_file, "w") as f:
            json.dump(manifest.to_dict(), f, indent=2)

    def _audit_backup(self, manifest: BackupManifest, operation: str):
        """Log backup operation to audit."""
        try:
            from .audit import AuditAction, audit_log

            action = (
                AuditAction.BACKUP_CREATE if operation == "create" else AuditAction.BACKUP_RESTORE
            )

            audit_log(
                action=action,
                actor_id="system",
                resource_type="backup",
                resource_id=manifest.id,
                details={
                    "type": manifest.backup_type.value,
                    "files": manifest.file_count,
                    "size": manifest.total_size,
                    "status": manifest.status.value,
                },
            )
        except Exception:
            # Audit is best-effort — never let it break a backup operation
            pass

    def list_backups(self) -> List[BackupManifest]:
        """List all available backups."""
        backups = []

        for manifest_file in self.config.path.glob("manifest_*.json"):
            try:
                with open(manifest_file) as f:
                    manifest = BackupManifest.from_dict(json.load(f))
                    backups.append(manifest)
            except Exception as e:
                logger.warning(f"Failed to read manifest {manifest_file}: {e}")

        # Sort by date descending
        backups.sort(key=lambda m: m.created_at, reverse=True)
        return backups

    def get_backup(self, backup_id: str) -> Optional[BackupManifest]:
        """Get a specific backup manifest."""
        manifest_file = self.config.path / f"manifest_{backup_id}.json"

        if manifest_file.exists():
            with open(manifest_file) as f:
                return BackupManifest.from_dict(json.load(f))

        return None

    def verify_backup(self, backup_id: str) -> Dict[str, Any]:
        """
        Verify backup integrity.

        Returns:
            Dict with verification result
        """
        manifest = self.get_backup(backup_id)
        if not manifest:
            return {"valid": False, "error": "Backup not found"}

        archive_path = self.config.path / f"familiar_backup_{backup_id}.tar.gz"
        if not archive_path.exists():
            return {"valid": False, "error": "Archive not found"}

        # Check archive checksum
        actual_checksum = self._file_checksum(archive_path)
        checksum_valid = True
        checksum_note = ""

        if manifest.encrypted:
            if self._fernet:
                # Decrypt to a temp file and verify integrity
                tmp_path = None
                try:
                    with tempfile.NamedTemporaryFile(delete=False, suffix=".tar.gz") as tmp:
                        tmp_path = Path(tmp.name)
                        shutil.copy2(archive_path, tmp_path)

                    # Fernet.decrypt() includes HMAC validation — raises InvalidToken on corruption
                    self._decrypt_file(tmp_path)

                    # Verify decrypted archive is valid tar.gz
                    with tarfile.open(tmp_path, "r:gz") as tar:
                        tar.getmembers()

                    # Compare pre-encryption checksum if available
                    if manifest.checksum_pre_encryption:
                        decrypted_checksum = self._file_checksum(tmp_path)
                        import hmac as _hmac

                        if _hmac.compare_digest(
                            decrypted_checksum, manifest.checksum_pre_encryption
                        ):
                            checksum_note = "Encrypted backup verified (decrypted checksum matches)"
                        else:
                            checksum_valid = False
                            checksum_note = (
                                f"Pre-encryption checksum mismatch: "
                                f"expected {manifest.checksum_pre_encryption}, "
                                f"got {decrypted_checksum}"
                            )
                    else:
                        checksum_note = "Encrypted backup decrypted and validated (no pre-encryption checksum stored)"
                except Exception as e:
                    checksum_valid = False
                    checksum_note = f"Encrypted backup verification failed: {e}"
                finally:
                    if tmp_path and tmp_path.exists():
                        tmp_path.unlink()
            else:
                checksum_note = "Decryption key required for full verification of encrypted backup"
        elif manifest.checksum:
            if actual_checksum != manifest.checksum:
                checksum_valid = False
                checksum_note = (
                    f"Checksum mismatch: expected {manifest.checksum}, got {actual_checksum}"
                )
            else:
                checksum_note = "Checksum verified"
        else:
            checksum_note = "No stored checksum to compare"

        result = {
            "checksum": actual_checksum,
            "valid": checksum_valid,
            "checksum_note": checksum_note,
            "backup_id": backup_id,
            "created_at": manifest.created_at.isoformat(),
            "size": manifest.total_size,
            "files": manifest.file_count,
            "encrypted": manifest.encrypted,
        }

        manifest.status = BackupStatus.VERIFIED
        self._save_manifest(manifest)

        return result

    def restore(
        self,
        backup_id: str,
        target_dir: Path = None,
        dry_run: bool = False,
    ) -> Dict[str, Any]:
        """
        Restore from a backup.

        Args:
            backup_id: ID of backup to restore
            target_dir: Optional target directory (default: original locations)
            dry_run: If True, only simulate restore

        Returns:
            Dict with restore result
        """
        manifest = self.get_backup(backup_id)
        if not manifest:
            raise ValueError(f"Backup not found: {backup_id}")

        archive_path = self.config.path / f"familiar_backup_{backup_id}.tar.gz"
        if not archive_path.exists():
            raise ValueError(f"Archive not found: {archive_path}")

        logger.info(f"Restoring backup: {backup_id} (dry_run={dry_run})")

        result = {
            "backup_id": backup_id,
            "files_restored": 0,
            "errors": [],
            "dry_run": dry_run,
        }

        # Create temp directory for extraction
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Copy archive (in case encrypted)
            temp_archive = temp_path / "archive.tar.gz"
            shutil.copy2(archive_path, temp_archive)

            # Decrypt if needed
            if manifest.encrypted:
                if not self._fernet:
                    raise ValueError("Backup is encrypted but no key provided")
                self._decrypt_file(temp_archive)

            # Extract
            extract_dir = temp_path / "extracted"
            extract_dir.mkdir()

            with tarfile.open(temp_archive, "r:gz") as tar:
                tar.extractall(extract_dir, filter="data")

            # Determine target
            if target_dir is None:
                from .paths import DATA_DIR, SKILLS_DIR

                target_data = Path(DATA_DIR)
                target_skills = Path(SKILLS_DIR)
            else:
                target_data = target_dir / "data"
                target_skills = target_dir / "skills"

            if not dry_run:
                # Restore config
                config_src = extract_dir / "config"
                if config_src.exists():
                    # Don't overwrite config by default
                    pass

                # Restore data
                data_src = extract_dir / "data"
                if data_src.exists():
                    target_data.mkdir(parents=True, exist_ok=True)
                    for item in data_src.iterdir():
                        dest = target_data / item.name
                        if item.is_file():
                            shutil.copy2(item, dest)
                            result["files_restored"] += 1
                        elif item.is_dir():
                            shutil.copytree(item, dest, dirs_exist_ok=True)
                            result["files_restored"] += len(list(item.rglob("*")))

                # Restore skills
                skills_src = extract_dir / "skills"
                if skills_src.exists():
                    target_skills.mkdir(parents=True, exist_ok=True)
                    shutil.copytree(skills_src, target_skills, dirs_exist_ok=True)
                    result["files_restored"] += len(list(skills_src.rglob("*")))
            else:
                # Dry run - just count files
                for item in extract_dir.rglob("*"):
                    if item.is_file():
                        result["files_restored"] += 1

        logger.info(f"Restore completed: {result['files_restored']} files")

        # Log to audit
        self._audit_backup(manifest, "restore")

        return result

    def delete_backup(self, backup_id: str) -> bool:
        """Delete a backup."""
        manifest = self.get_backup(backup_id)
        if not manifest:
            return False

        # Delete archive
        archive_path = self.config.path / f"familiar_backup_{backup_id}.tar.gz"
        if archive_path.exists():
            archive_path.unlink()

        # Delete manifest
        manifest_path = self.config.path / f"manifest_{backup_id}.json"
        if manifest_path.exists():
            manifest_path.unlink()

        logger.info(f"Deleted backup: {backup_id}")
        return True

    def cleanup_old_backups(self) -> int:
        """
        Delete backups older than retention period.

        Returns:
            Number of backups deleted
        """
        cutoff = datetime.now(_UTC).replace(tzinfo=None) - timedelta(
            days=self.config.retention_days
        )
        deleted = 0

        for manifest in self.list_backups():
            if manifest.created_at < cutoff:
                self.delete_backup(manifest.id)
                deleted += 1

        if deleted:
            logger.info(f"Cleaned up {deleted} old backups")

        return deleted


# ============================================================
# CLI
# ============================================================


def main():
    """Backup CLI."""
    import argparse

    parser = argparse.ArgumentParser(description="Familiar Backup Tool")
    subparsers = parser.add_subparsers(dest="command")

    # Create
    create_parser = subparsers.add_parser("create", help="Create backup")
    create_parser.add_argument("--type", choices=["full", "config", "data"], default="full")

    # List
    subparsers.add_parser("list", help="List backups")

    # Restore
    restore_parser = subparsers.add_parser("restore", help="Restore backup")
    restore_parser.add_argument("backup_id", help="Backup ID to restore")
    restore_parser.add_argument("--dry-run", action="store_true")
    restore_parser.add_argument("--target", help="Target directory")

    # Verify
    verify_parser = subparsers.add_parser("verify", help="Verify backup")
    verify_parser.add_argument("backup_id", help="Backup ID to verify")

    # Delete
    delete_parser = subparsers.add_parser("delete", help="Delete backup")
    delete_parser.add_argument("backup_id", help="Backup ID to delete")

    # Cleanup
    subparsers.add_parser("cleanup", help="Delete old backups")

    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO)
    manager = BackupManager()

    if args.command == "create":
        type_map = {
            "full": BackupType.FULL,
            "config": BackupType.CONFIG_ONLY,
            "data": BackupType.DATA_ONLY,
        }
        manifest = manager.create_backup(backup_type=type_map[args.type])
        print(f"Backup created: {manifest.id}")

    elif args.command == "list":
        backups = manager.list_backups()
        if not backups:
            print("No backups found")
        else:
            print(f"{'ID':<20} {'Type':<10} {'Status':<12} {'Size':<12} {'Created'}")
            print("-" * 80)
            for b in backups:
                size = f"{b.total_size / 1024:.1f} KB" if b.total_size else "?"
                print(
                    f"{b.id:<20} {b.backup_type.value:<10} {b.status.value:<12} {size:<12} {b.created_at}"
                )

    elif args.command == "restore":
        target = Path(args.target) if args.target else None
        result = manager.restore(args.backup_id, target_dir=target, dry_run=args.dry_run)
        print(f"Restored {result['files_restored']} files")

    elif args.command == "verify":
        result = manager.verify_backup(args.backup_id)
        print(json.dumps(result, indent=2))

    elif args.command == "delete":
        if manager.delete_backup(args.backup_id):
            print(f"Deleted: {args.backup_id}")
        else:
            print("Backup not found")

    elif args.command == "cleanup":
        deleted = manager.cleanup_old_backups()
        print(f"Deleted {deleted} old backups")

    else:
        parser.print_help()


if __name__ == "__main__":
    main()
