#!/usr/bin/env python3
"""
Session and state backup automation for Ezra.
Backs up critical files: sessions, memory, config, state.db.

Epic: EZRA-SELF-001 / Phase 4 - Session Management
Author: Ezra (self-improvement)
"""

import json
import os
import shutil
import tarfile
import time
from datetime import datetime
from pathlib import Path


class SessionBackup:
    """Automated backup of Ezra's state and sessions."""

    def __init__(
        self,
        home_dir: str = None,
        backup_dir: str = None,
        max_backups: int = 10,
    ):
        self.home_dir = Path(home_dir or "/root/wizards/ezra/home")
        self.backup_dir = Path(backup_dir or "/root/wizards/ezra/backups")
        self.max_backups = max_backups
        self.backup_dir.mkdir(parents=True, exist_ok=True)

    # Files/patterns to back up
    CRITICAL_FILES = [
        "config.yaml",
        "memories/MEMORY.md",
        "memories/USER.md",
        "state.db",
        "channel_directory.json",
        "gateway_state.json",
        "cron/jobs.json",
    ]

    CRITICAL_DIRS = [
        "sessions",
    ]

    def create_backup(self, label: str = None) -> dict:
        """Create a compressed backup of critical state."""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        label = label or "auto"
        filename = f"ezra-backup-{timestamp}-{label}.tar.gz"
        filepath = self.backup_dir / filename

        files_included = []
        files_missing = []
        total_size = 0

        with tarfile.open(filepath, "w:gz") as tar:
            # Individual critical files
            for rel_path in self.CRITICAL_FILES:
                full_path = self.home_dir / rel_path
                if full_path.exists():
                    tar.add(full_path, arcname=rel_path)
                    size = full_path.stat().st_size
                    files_included.append({"path": rel_path, "size": size})
                    total_size += size
                else:
                    files_missing.append(rel_path)

            # Session files (only metadata, not full JSONL)
            sessions_dir = self.home_dir / "sessions"
            if sessions_dir.exists():
                # Include session index
                sessions_json = sessions_dir / "sessions.json"
                if sessions_json.exists():
                    tar.add(sessions_json, arcname="sessions/sessions.json")
                    files_included.append({"path": "sessions/sessions.json", "size": sessions_json.stat().st_size})

                # Include session metadata (small files)
                for f in sessions_dir.glob("session_*.json"):
                    if f.stat().st_size < 100_000:  # Skip huge session files
                        tar.add(f, arcname=f"sessions/{f.name}")
                        files_included.append({"path": f"sessions/{f.name}", "size": f.stat().st_size})
                        total_size += f.stat().st_size

        backup_size = filepath.stat().st_size

        result = {
            "filename": filename,
            "path": str(filepath),
            "backup_size": backup_size,
            "backup_size_human": self._human_size(backup_size),
            "source_size": total_size,
            "files_included": len(files_included),
            "files_missing": files_missing,
            "timestamp": timestamp,
        }

        # Rotate old backups
        self._rotate_backups()

        return result

    def _rotate_backups(self):
        """Remove old backups beyond max_backups."""
        backups = sorted(
            self.backup_dir.glob("ezra-backup-*.tar.gz"),
            key=lambda p: p.stat().st_mtime,
            reverse=True,
        )
        for old in backups[self.max_backups:]:
            old.unlink()

    def list_backups(self) -> list[dict]:
        """List existing backups."""
        backups = []
        for f in sorted(self.backup_dir.glob("ezra-backup-*.tar.gz"), reverse=True):
            stat = f.stat()
            backups.append({
                "filename": f.name,
                "size": self._human_size(stat.st_size),
                "created": datetime.fromtimestamp(stat.st_mtime).isoformat(),
                "age_hours": round((time.time() - stat.st_mtime) / 3600, 1),
            })
        return backups

    def restore_backup(self, filename: str, dry_run: bool = True) -> dict:
        """Restore from a backup. Use dry_run=True to preview."""
        filepath = self.backup_dir / filename
        if not filepath.exists():
            return {"error": f"Backup not found: {filename}"}

        with tarfile.open(filepath, "r:gz") as tar:
            members = tar.getmembers()

            if dry_run:
                return {
                    "mode": "dry_run",
                    "filename": filename,
                    "files": [m.name for m in members],
                    "total_files": len(members),
                }

            # Actual restore
            tar.extractall(path=str(self.home_dir))
            return {
                "mode": "restored",
                "filename": filename,
                "files_restored": len(members),
            }

    def check_freshness(self) -> dict:
        """Check if backups are fresh enough."""
        backups = self.list_backups()
        if not backups:
            return {"fresh": False, "reason": "No backups exist", "latest": None}

        latest = backups[0]
        age = latest["age_hours"]
        return {
            "fresh": age < 24,
            "latest": latest["filename"],
            "age_hours": age,
            "total_backups": len(backups),
        }

    @staticmethod
    def _human_size(size: int) -> str:
        for unit in ["B", "KB", "MB", "GB"]:
            if size < 1024:
                return f"{size:.1f}{unit}"
            size /= 1024
        return f"{size:.1f}TB"


if __name__ == "__main__":
    backup = SessionBackup()

    # Create a backup
    result = backup.create_backup("manual")
    print(f"Created: {result['filename']} ({result['backup_size_human']})")
    print(f"Files: {result['files_included']} included, {len(result['files_missing'])} missing")
    if result["files_missing"]:
        print(f"Missing: {', '.join(result['files_missing'])}")

    # List backups
    print("\nExisting backups:")
    for b in backup.list_backups():
        print(f"  {b['filename']} - {b['size']} ({b['age_hours']}h ago)")
