"""SOAGILA Backup & Restore — Encrypted local backup of project memory.

Creates AES-256-GCM encrypted copies of the SQLite database.
Stored at ~/.soagila/backups/{project_hash}/{timestamp}.soagila.bak
"""

import os
import json
import shutil
import logging
from pathlib import Path
from datetime import datetime, timezone
from uuid import uuid4

from soagila.config import get_soagila_home, get_project_hash, get_project_dir
from soagila.core.encryption.simulated_he import get_encryption_engine

logger = logging.getLogger("soagila.backup")

MAX_BACKUPS = 10  # Keep last N backups per project


def get_backup_dir(project_hash: str | None = None) -> Path:
    """Get backup directory for a project."""
    if project_hash is None:
        project_hash = get_project_hash()
    backup_dir = get_soagila_home() / "backups" / project_hash
    backup_dir.mkdir(parents=True, exist_ok=True)
    return backup_dir


async def create_backup() -> dict:
    """Create an encrypted backup of the current project's SQLite database.

    Returns backup metadata.
    """
    from soagila.db.sqlite_init import get_db

    project_hash = get_project_hash()
    project_dir = get_project_dir()
    db_path = project_dir / "soagila.db"

    if not db_path.exists():
        return {"error": "No database to backup", "status": "failed"}

    # Flush WAL to main database file
    db = await get_db()
    await db.execute("PRAGMA wal_checkpoint(TRUNCATE)")

    # Read the database file
    db_bytes = db_path.read_bytes()

    # Encrypt the backup
    engine = get_encryption_engine()
    backup_key_ref = engine.generate_key("backup")
    encrypted = engine.encrypt(db_bytes.hex(), backup_key_ref)

    # Write encrypted backup
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
    backup_dir = get_backup_dir(project_hash)
    backup_filename = f"{timestamp}.soagila.bak"
    backup_path = backup_dir / backup_filename

    backup_path.write_bytes(encrypted)

    # Write metadata alongside
    meta = {
        "project_hash": project_hash,
        "project_cwd": os.environ.get("SOAGILA_PROJECT_CWD", ""),
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "original_size_kb": round(len(db_bytes) / 1024, 1),
        "encrypted_size_kb": round(len(encrypted) / 1024, 1),
        "key_ref": backup_key_ref,
        "filename": backup_filename,
    }
    meta_path = backup_dir / f"{timestamp}.meta.json"
    meta_path.write_text(json.dumps(meta, indent=2))

    # Cleanup old backups (keep last MAX_BACKUPS)
    _cleanup_old_backups(backup_dir)

    logger.info(f"Backup created: {backup_filename} ({meta['encrypted_size_kb']} KB)")

    return {
        "status": "success",
        "backup_path": str(backup_path),
        "original_size_kb": meta["original_size_kb"],
        "encrypted_size_kb": meta["encrypted_size_kb"],
        "timestamp": meta["timestamp"],
    }


async def restore_backup(backup_filename: str | None = None) -> dict:
    """Restore a project database from an encrypted backup.

    If no filename given, uses the most recent backup.
    """
    from soagila.db.sqlite_init import close_db

    project_hash = get_project_hash()
    backup_dir = get_backup_dir(project_hash)
    project_dir = get_project_dir()
    db_path = project_dir / "soagila.db"

    # Find backup file
    if backup_filename:
        backup_path = backup_dir / backup_filename
        meta_name = backup_filename.replace(".soagila.bak", ".meta.json")
    else:
        # Use most recent
        backups = sorted(backup_dir.glob("*.soagila.bak"), reverse=True)
        if not backups:
            return {"error": "No backups found", "status": "failed"}
        backup_path = backups[0]
        meta_name = backup_path.stem.replace(".soagila", "") + ".meta.json"

    if not backup_path.exists():
        return {"error": f"Backup not found: {backup_path.name}", "status": "failed"}

    # Read metadata for key_ref
    meta_path = backup_dir / meta_name
    if not meta_path.exists():
        return {"error": "Backup metadata missing — cannot decrypt", "status": "failed"}

    meta = json.loads(meta_path.read_text())
    key_ref = meta["key_ref"]

    # Decrypt
    engine = get_encryption_engine()
    if not engine.key_exists(key_ref):
        return {"error": "Encryption key not found — backup cannot be decrypted", "status": "failed"}

    encrypted = backup_path.read_bytes()
    decrypted_hex = engine.decrypt(encrypted, key_ref)
    db_bytes = bytes.fromhex(decrypted_hex)

    # Close current database connection
    await close_db()

    # Backup current DB before overwriting
    if db_path.exists():
        pre_restore = db_path.with_suffix(".pre-restore.db")
        shutil.copy2(db_path, pre_restore)

    # Write restored database
    db_path.write_bytes(db_bytes)

    logger.info(f"Database restored from {backup_path.name}")

    return {
        "status": "success",
        "restored_from": backup_path.name,
        "restored_size_kb": round(len(db_bytes) / 1024, 1),
        "timestamp": meta["timestamp"],
        "message": "Database restored. Restart MCP server to use restored data.",
    }


def list_backups() -> list[dict]:
    """List all available backups for the current project."""
    project_hash = get_project_hash()
    backup_dir = get_backup_dir(project_hash)

    backups = []
    for meta_path in sorted(backup_dir.glob("*.meta.json"), reverse=True):
        try:
            meta = json.loads(meta_path.read_text())
            bak_path = backup_dir / meta["filename"]
            meta["exists"] = bak_path.exists()
            backups.append(meta)
        except Exception:
            pass

    return backups


def _cleanup_old_backups(backup_dir: Path):
    """Remove old backups, keeping only the last MAX_BACKUPS."""
    backups = sorted(backup_dir.glob("*.soagila.bak"))
    while len(backups) > MAX_BACKUPS:
        old = backups.pop(0)
        old.unlink(missing_ok=True)
        # Also remove metadata
        meta = old.with_suffix("").with_suffix(".meta.json")
        meta.unlink(missing_ok=True)
        logger.debug(f"Cleaned up old backup: {old.name}")
