"""cortex/cli/backup_cmds.py — backup and restore CLI commands.

Adds two top-level commands to the ``evaos`` CLI:

  evaos backup   — create a timestamped tar.gz backup with SHA-256 checksum
  evaos restore  — verify checksum and restore a backup archive
  evaos backups  — list existing backups (and optionally prune)
"""
from __future__ import annotations

import sys
from pathlib import Path
from typing import Optional

import click

import cortex.cli.app as _app

main = _app.main


# ---------------------------------------------------------------------------
# backup
# ---------------------------------------------------------------------------

@main.command("backup")
@click.option("--output-dir", "output_dir",
              default="./backups",
              envvar="CORTEX_BACKUP_DIR",
              help="Directory where backup archives are written (default: ./backups).")
@click.option("--name-prefix", "name_prefix",
              default="cortex",
              help="Filename prefix for the archive (default: cortex).")
@click.option("--include-config", "include_config",
              default=None, type=click.Path(),
              help="Path to cortex.toml to bundle inside the backup.")
@click.option("--keep", "keep",
              default=None, type=int,
              help="Auto-prune: keep only N most-recent backups after this run.")
@click.pass_context
def backup_cmd(
    ctx: click.Context,
    output_dir: str,
    name_prefix: str,
    include_config: Optional[str],
    keep: Optional[int],
) -> None:
    """Create a compressed, checksummed backup of the Cortex database.

    The backup is written to OUTPUT_DIR as a timestamped tar.gz archive
    alongside a SHA-256 checksum sidecar (.tar.gz.sha256).

    WAL checkpointing is performed before copying to ensure a consistent
    snapshot of the database.

    Examples:

    \b
        evaos backup
        evaos backup --output-dir /var/backups/cortex --keep 7
        evaos backup --include-config cortex.toml
        CORTEX_DB=/data/cortex.db evaos backup --output-dir /mnt/backups
    """
    from cortex.storage.backup import backup_database

    db_path_str: Optional[str] = ctx.obj.get("db") if ctx.obj else None
    if db_path_str is None:
        # Try config or default
        db_path = Path("cortex.db")
        config_path = (ctx.obj.get("config") if ctx.obj else None) or "cortex.toml"
        if Path(config_path).exists():
            try:
                from cortex.config import CortexConfig
                cfg = CortexConfig.from_toml(config_path)
                if cfg.db_path:
                    db_path = Path(cfg.db_path)
            except Exception:
                pass
    else:
        db_path = Path(db_path_str)

    if not db_path.exists():
        _app._err(f"Database not found: {db_path}")
        _app._warn("Use --db or CORTEX_DB to specify the database path.")
        sys.exit(1)

    _app._section("Cortex Backup")
    _app._print(f"Database : [bold]{db_path}[/bold]" if _app._RICH else f"Database : {db_path}")
    _app._print(f"Output   : [bold]{output_dir}[/bold]" if _app._RICH else f"Output   : {output_dir}")

    try:
        archive, checksum = backup_database(
            db_path=db_path,
            output_dir=Path(output_dir),
            name_prefix=name_prefix,
            include_config=Path(include_config) if include_config else None,
            keep=keep,
        )
    except Exception as exc:
        _app._err(f"Backup failed: {exc}")
        sys.exit(1)

    _app._ok(f"Archive  : {archive}")
    _app._ok(f"Checksum : {checksum}")
    if keep is not None:
        _app._print(f"  (retained last {keep} backups)")


# ---------------------------------------------------------------------------
# restore
# ---------------------------------------------------------------------------

@main.command("restore")
@click.argument("archive", type=click.Path(exists=True))
@click.option("--force", is_flag=True, default=False,
              help="Skip active-connection check; proceed even if checksum fails.")
@click.option("--skip-checksum", "skip_checksum", is_flag=True, default=False,
              help="Bypass SHA-256 verification (not recommended).")
@click.pass_context
def restore_cmd(
    ctx: click.Context,
    archive: str,
    force: bool,
    skip_checksum: bool,
) -> None:
    """Restore the Cortex database from a backup archive.

    ARCHIVE must be a path to a .tar.gz backup created by ``evaos backup``.

    The restore process:
      1. Verifies the SHA-256 checksum sidecar (unless --skip-checksum)
      2. Extracts the archive to a temporary directory
      3. Backs up the existing database to <db>.bak
      4. Copies the restored database into place
      5. Runs PRAGMA integrity_check — rolls back if it fails

    Examples:

    \b
        evaos restore backups/cortex_20260101T120000Z.tar.gz
        evaos --db /data/cortex.db restore backups/cortex_20260101T120000Z.tar.gz
        evaos restore cortex_backup.tar.gz --force
    """
    from cortex.storage.backup import restore_database

    db_path_str: Optional[str] = ctx.obj.get("db") if ctx.obj else None
    if db_path_str is None:
        db_path = Path("cortex.db")
        config_path = (ctx.obj.get("config") if ctx.obj else None) or "cortex.toml"
        if Path(config_path).exists():
            try:
                from cortex.config import CortexConfig
                cfg = CortexConfig.from_toml(config_path)
                if cfg.db_path:
                    db_path = Path(cfg.db_path)
            except Exception:
                pass
    else:
        db_path = Path(db_path_str)

    archive_path = Path(archive)

    if not skip_checksum and not force:
        _app._warn("Checksum will be verified before restoring.")

    _app._section("Cortex Restore")
    _app._print(f"Archive  : [bold]{archive_path}[/bold]" if _app._RICH else f"Archive  : {archive_path}")
    _app._print(f"Target   : [bold]{db_path}[/bold]" if _app._RICH else f"Target   : {db_path}")

    if not skip_checksum:
        _app._print("Verifying checksum…")

    try:
        restore_database(
            archive_path=archive_path,
            db_path=db_path,
            force=force,
            skip_checksum=skip_checksum,
        )
    except (FileNotFoundError, ValueError, RuntimeError) as exc:
        _app._err(f"Restore failed: {exc}")
        sys.exit(1)
    except Exception as exc:
        _app._err(f"Unexpected error during restore: {exc}")
        sys.exit(1)

    _app._ok(f"Restored successfully → {db_path}")
    _app._ok("Integrity check passed")


# ---------------------------------------------------------------------------
# backups (list / prune)
# ---------------------------------------------------------------------------

@main.command("backups")
@click.option("--output-dir", "output_dir",
              default="./backups",
              envvar="CORTEX_BACKUP_DIR",
              help="Directory to search for backup archives (default: ./backups).")
@click.option("--name-prefix", "name_prefix",
              default="cortex",
              help="Filename prefix to filter by (default: cortex).")
@click.option("--prune", "keep",
              default=None, type=int,
              help="Prune: keep only N most-recent backups and delete the rest.")
def backups_cmd(output_dir: str, name_prefix: str, keep: Optional[int]) -> None:
    """List (and optionally prune) existing Cortex backup archives.

    Examples:

    \b
        evaos backups
        evaos backups --output-dir /var/backups/cortex
        evaos backups --prune 5
    """
    from cortex.storage.backup import list_backups, prune_backups

    output_path = Path(output_dir)
    archives = list_backups(output_path, name_prefix=name_prefix)

    if not archives:
        _app._warn(f"No backups found in {output_dir}")
        return

    _app._section(f"Cortex Backups — {output_dir}")
    rows = []
    for arch in reversed(archives):  # newest first
        size_kb = arch.stat().st_size / 1024
        rows.append((arch.name, f"{size_kb:.1f} KB"))
    _app._kv_table(rows, title=f"Found {len(archives)} backup(s)")

    if keep is not None:
        removed = prune_backups(output_path, name_prefix=name_prefix, keep=keep)
        if removed:
            _app._print(f"Pruned {len(removed)} old backup(s):")
            for r in removed:
                _app._print(f"  - {r.name}")
        else:
            _app._ok(f"No backups to prune (keeping last {keep})")
