"""AYN Antivirus — CLI interface.

Main entry point for all user-facing commands. Built with Click and Rich.
"""

from __future__ import annotations

import json
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Optional

import click
from rich.console import Console
from rich.panel import Panel
from rich.progress import (
    BarColumn,
    MofNCompleteColumn,
    Progress,
    SpinnerColumn,
    TextColumn,
    TimeElapsedColumn,
    TimeRemainingColumn,
)
from rich.table import Table
from rich.text import Text
from rich import box

from ayn_antivirus import __version__
from ayn_antivirus.config import Config
from ayn_antivirus.constants import (
    DEFAULT_DB_PATH,
    DEFAULT_LOG_PATH,
    DEFAULT_QUARANTINE_PATH,
    DEFAULT_SCAN_PATH,
    HIGH_CPU_THRESHOLD,
)
from ayn_antivirus.utils.helpers import format_size, format_duration

# ---------------------------------------------------------------------------
# Console singletons
# ---------------------------------------------------------------------------
console = Console(stderr=True)
out = Console()

# ---------------------------------------------------------------------------
# Severity helpers
# ---------------------------------------------------------------------------
SEVERITY_STYLES = {
    "CRITICAL": "bold red",
    "HIGH": "bold yellow",
    "MEDIUM": "bold blue",
    "LOW": "bold green",
}


def severity_text(level: str) -> Text:
    """Return a Rich Text object coloured by severity."""
    return Text(level, style=SEVERITY_STYLES.get(level.upper(), "white"))


# ---------------------------------------------------------------------------
# Banner
# ---------------------------------------------------------------------------
BANNER = r"""
[bold cyan]     ██████╗ ██╗   ██╗███╗   ██╗
    ██╔══██╗╚██╗ ██╔╝████╗  ██║
    ███████║ ╚████╔╝ ██╔██╗ ██║
    ██╔══██║  ╚██╔╝  ██║╚██╗██║
    ██║  ██║   ██║   ██║ ╚████║
    ╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═══╝[/bold cyan]
[bold white]    ⚔️  AYN ANTIVIRUS v{version}  ⚔️[/bold white]
[dim]    Server Protection Suite[/dim]
""".strip()


def print_banner() -> None:
    """Print the AYN ASCII banner."""
    console.print()
    console.print(BANNER.format(version=__version__))
    console.print()


# ---------------------------------------------------------------------------
# Progress bar factory
# ---------------------------------------------------------------------------
def make_progress(**kwargs) -> Progress:
    """Return a pre-configured Rich progress bar."""
    return Progress(
        SpinnerColumn(),
        TextColumn("[bold blue]{task.description}"),
        BarColumn(bar_width=40),
        MofNCompleteColumn(),
        TimeElapsedColumn(),
        TimeRemainingColumn(),
        console=console,
        **kwargs,
    )


# ---------------------------------------------------------------------------
# Root group
# ---------------------------------------------------------------------------
@click.group(invoke_without_command=True)
@click.option(
    "--config",
    "config_path",
    type=click.Path(exists=True, dir_okay=False),
    default=None,
    help="Path to a YAML configuration file.",
)
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output.")
@click.version_option(version=__version__, prog_name="ayn-antivirus")
@click.pass_context
def main(ctx: click.Context, config_path: Optional[str], verbose: bool) -> None:
    """AYN Antivirus — Comprehensive server protection suite.

    Anti-malware · Anti-spyware · Anti-cryptominer · Rootkit detection
    """
    ctx.ensure_object(dict)
    ctx.obj = Config.load(config_path)
    ctx.obj._verbose = verbose  # type: ignore[attr-defined]

    if ctx.invoked_subcommand is None:
        print_banner()
        click.echo(ctx.get_help())


# ===================================================================
# scan
# ===================================================================
@main.command()
@click.option(
    "--path",
    "scan_path",
    type=click.Path(exists=True),
    default=None,
    help="Target path to scan (default: configured scan paths).",
)
@click.option("--quick", is_flag=True, help="Quick scan — critical directories only.")
@click.option("--deep", is_flag=True, help="Deep scan — includes memory and hidden artifacts.")
@click.option(
    "--file",
    "single_file",
    type=click.Path(exists=True, dir_okay=False),
    default=None,
    help="Scan a single file.",
)
@click.option(
    "--exclude",
    multiple=True,
    help="Glob pattern(s) to exclude from the scan.",
)
@click.pass_obj
def scan(
    cfg: Config,
    scan_path: Optional[str],
    quick: bool,
    deep: bool,
    single_file: Optional[str],
    exclude: tuple,
) -> None:
    """Run a file-system threat scan.

    By default, all configured scan paths are checked. Use --quick for a fast
    pass over /tmp, /var/tmp, /dev/shm, and crontabs, or --deep to also inspect
    running process memory regions.

    \b
    Examples
    --------
      ayn-antivirus scan
      ayn-antivirus scan --path /home --exclude '*.log'
      ayn-antivirus scan --quick
      ayn-antivirus scan --file /tmp/suspicious.bin
    """
    from ayn_antivirus.core.engine import ScanEngine, FileScanResult

    print_banner()

    if quick and deep:
        console.print("[red]Error:[/red] --quick and --deep are mutually exclusive.")
        raise SystemExit(1)

    engine = ScanEngine(cfg)

    # Determine scan mode label
    if single_file:
        mode = "single-file"
        targets = [single_file]
    elif quick:
        mode = "quick"
        targets = ["/tmp", "/var/tmp", "/dev/shm", "/var/spool/cron", "/etc/cron.d"]
    elif scan_path:
        mode = "targeted"
        targets = [scan_path]
    else:
        mode = "deep" if deep else "full"
        targets = cfg.scan_paths

    exclude_patterns = list(exclude) + cfg.exclude_paths

    console.print(
        Panel(
            f"[bold]Mode:[/bold]     {mode}\n"
            f"[bold]Targets:[/bold]  {', '.join(targets)}\n"
            f"[bold]Exclude:[/bold]  {', '.join(exclude_patterns) or '(none)'}\n"
            f"[bold]YARA:[/bold]     {'enabled' if cfg.enable_yara else 'disabled'}\n"
            f"[bold]Heuristics:[/bold] {'enabled' if cfg.enable_heuristics else 'disabled'}",
            title="[bold cyan]Scan Configuration[/bold cyan]",
            border_style="cyan",
        )
    )

    # --- Single file scan ---
    if single_file:
        console.print()
        with make_progress(transient=True) as progress:
            task = progress.add_task("Scanning file…", total=1)
            result = engine.scan_file(single_file)
            progress.advance(task)

        if result.threats:
            _print_threat_table_from_engine(result.threats)
        _print_scan_summary(
            scanned=1 if result.scanned else 0,
            errors=1 if result.error else 0,
            threat_count=len(result.threats),
            elapsed=0.0,
        )
        return

    # --- Quick scan ---
    if quick:
        console.print()
        start = time.monotonic()
        scan_result = engine.quick_scan(
            callback=lambda _fr: None,
        )
        elapsed = time.monotonic() - start

        if scan_result.threats:
            _print_threat_table_from_engine(scan_result.threats)
        _print_scan_summary(
            scanned=scan_result.files_scanned,
            errors=scan_result.files_skipped,
            threat_count=len(scan_result.threats),
            elapsed=elapsed,
        )
        return

    # --- Path / full scan ---
    console.print()
    all_threats = []
    total_scanned = 0
    total_skipped = 0
    start = time.monotonic()

    for target in targets:
        tp = Path(target)
        if not tp.exists():
            console.print(f"[yellow]⚠ Path does not exist:[/yellow] {target}")
            continue

        scan_result = engine.scan_path(target, recursive=True, quick=False)
        total_scanned += scan_result.files_scanned
        total_skipped += scan_result.files_skipped
        all_threats.extend(scan_result.threats)

    elapsed = time.monotonic() - start

    if all_threats:
        _print_threat_table_from_engine(all_threats)
    _print_scan_summary(
        scanned=total_scanned,
        errors=total_skipped,
        threat_count=len(all_threats),
        elapsed=elapsed,
    )


def _print_scan_summary(
    scanned: int, errors: int, threat_count: int, elapsed: float
) -> None:
    """Render the post-scan summary panel."""
    status_colour = "green" if threat_count == 0 else "red"
    status_icon = "✅" if threat_count == 0 else "🚨"

    lines = [
        f"[bold]Files scanned:[/bold]  {scanned}",
        f"[bold]Errors:[/bold]         {errors}",
        f"[bold]Threats found:[/bold]  [{status_colour}]{threat_count}[/{status_colour}]",
        f"[bold]Elapsed:[/bold]        {format_duration(elapsed)}",
    ]

    console.print()
    console.print(
        Panel(
            "\n".join(lines),
            title=f"{status_icon}  [bold {status_colour}]Scan Complete[/bold {status_colour}]",
            border_style=status_colour,
        )
    )


def _print_threat_table_from_engine(threats: list) -> None:
    """Render a table of ThreatInfo objects from the engine."""
    table = Table(
        title="Threats Detected",
        box=box.ROUNDED,
        show_lines=True,
        title_style="bold red",
    )
    table.add_column("#", style="dim", width=4)
    table.add_column("Severity", width=10)
    table.add_column("File", style="cyan", max_width=60)
    table.add_column("Threat", style="white")
    table.add_column("Type", style="dim")
    table.add_column("Detector", style="dim")

    for idx, t in enumerate(threats, 1):
        sev = t.severity.name if hasattr(t.severity, "name") else str(t.severity)
        ttype = t.threat_type.name if hasattr(t.threat_type, "name") else str(t.threat_type)
        table.add_row(
            str(idx),
            severity_text(sev),
            t.path,
            t.threat_name,
            ttype,
            t.detector_name,
        )

    console.print()
    console.print(table)


# ===================================================================
# scan-processes
# ===================================================================
@main.command("scan-processes")
@click.pass_obj
def scan_processes(cfg: Config) -> None:
    """Scan running processes for malware, miners, and suspicious activity.

    Inspects process names, command lines, CPU usage, and open network
    connections against known cryptominer signatures and heuristics.
    """
    from ayn_antivirus.core.engine import ScanEngine

    print_banner()

    console.print(
        Panel(
            "[bold]Checking running processes…[/bold]",
            title="[bold cyan]Process Scanner[/bold cyan]",
            border_style="cyan",
        )
    )

    engine = ScanEngine(cfg)

    with make_progress(transient=True) as progress:
        task = progress.add_task("Scanning processes…", total=None)
        result = engine.scan_processes()
        progress.update(task, total=result.processes_scanned, completed=result.processes_scanned)

    if not result.threats:
        console.print(
            Panel(
                f"[green]Scanned {result.processes_scanned} processes — no threats.[/green]",
                title="✅  [bold green]All Clear[/bold green]",
                border_style="green",
            )
        )
        return

    table = Table(
        title="Suspicious Processes",
        box=box.ROUNDED,
        show_lines=True,
        title_style="bold red",
    )
    table.add_column("PID", style="dim", width=8)
    table.add_column("Severity", width=10)
    table.add_column("Process", style="cyan")
    table.add_column("CPU %", style="white", justify="right")
    table.add_column("Details", style="white", max_width=50)

    for t in result.threats:
        sev = t.severity.name if hasattr(t.severity, "name") else str(t.severity)
        table.add_row(
            str(t.pid),
            severity_text(sev),
            t.name,
            f"{t.cpu_percent:.1f}%",
            t.details,
        )

    console.print()
    console.print(table)
    console.print(
        f"\n[bold red]🚨 {len(result.threats)} suspicious process(es) found.[/bold red]"
    )


# ===================================================================
# scan-network
# ===================================================================
@main.command("scan-network")
@click.pass_obj
def scan_network(cfg: Config) -> None:
    """Scan active network connections for suspicious activity.

    Checks for connections to known mining pools, suspicious ports, and
    unexpected outbound traffic patterns.
    """
    from ayn_antivirus.core.engine import ScanEngine

    print_banner()

    console.print(
        Panel(
            "[bold]Inspecting network connections…[/bold]",
            title="[bold cyan]Network Scanner[/bold cyan]",
            border_style="cyan",
        )
    )

    engine = ScanEngine(cfg)

    with make_progress(transient=True) as progress:
        task = progress.add_task("Analysing connections…", total=None)
        result = engine.scan_network()
        progress.update(task, total=result.connections_scanned, completed=result.connections_scanned)

    if not result.threats:
        console.print(
            Panel(
                f"[green]Scanned {result.connections_scanned} connections — no threats.[/green]",
                title="✅  [bold green]Network Clear[/bold green]",
                border_style="green",
            )
        )
        return

    table = Table(
        title="Suspicious Connections",
        box=box.ROUNDED,
        show_lines=True,
        title_style="bold red",
    )
    table.add_column("PID", style="dim", width=8)
    table.add_column("Severity", width=10)
    table.add_column("Local", style="cyan")
    table.add_column("Remote", style="red")
    table.add_column("Process", style="white")
    table.add_column("Details", style="white", max_width=45)

    for t in result.threats:
        sev = t.severity.name if hasattr(t.severity, "name") else str(t.severity)
        table.add_row(
            str(t.pid or "?"),
            severity_text(sev),
            t.local_addr,
            t.remote_addr,
            t.process_name,
            t.details,
        )

    console.print()
    console.print(table)
    console.print(
        f"\n[bold red]🚨 {len(result.threats)} suspicious connection(s) found.[/bold red]"
    )


# ===================================================================
# scan-containers
# ===================================================================
@main.command("scan-containers")
@click.option(
    "--runtime",
    type=click.Choice(["all", "docker", "podman", "lxc"]),
    default="all",
    help="Container runtime to scan.",
)
@click.option("--container", default=None, help="Scan a specific container by ID or name.")
@click.option("--include-stopped", is_flag=True, help="Include stopped containers.")
@click.pass_obj
def scan_containers(cfg: Config, runtime: str, container: Optional[str], include_stopped: bool) -> None:
    """Scan Docker/Podman/LXC containers for threats.

    Detects cryptominers, malware, reverse shells, misconfigurations,
    and suspicious SUID binaries inside running containers.

    \b
    Examples
    --------
      ayn-antivirus scan-containers
      ayn-antivirus scan-containers --runtime docker
      ayn-antivirus scan-containers --container my-web-app
    """
    from ayn_antivirus.scanners.container_scanner import ContainerScanner

    print_banner()

    scanner = ContainerScanner()

    if not scanner.available_runtimes:
        console.print("[yellow]\u26a0 No container runtimes found (docker/podman/lxc)[/yellow]")
        console.print("[dim]Install Docker, Podman, or LXC to use container scanning.[/dim]")
        return

    console.print(f"[cyan]\U0001f433 Available runtimes:[/cyan] {', '.join(scanner.available_runtimes)}")

    if container:
        console.print(f"\n[bold]Scanning container: {container}[/bold]")
    else:
        console.print(f"\n[bold]Scanning {runtime} containers\u2026[/bold]")

    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        BarColumn(),
        TimeElapsedColumn(),
        console=console,
    ) as progress:
        task = progress.add_task("Scanning containers\u2026", total=None)
        if container:
            result = scanner.scan_container(container)
        else:
            result = scanner.scan(runtime)
        progress.update(task, completed=100, total=100)

    # -- Containers table --
    if result.containers:
        console.print(f"\n[bold green]\U0001f4e6 Containers Found: {result.containers_found}[/bold green]")
        table = Table(title="Containers", box=box.ROUNDED, border_style="blue")
        table.add_column("ID", style="cyan", max_width=12)
        table.add_column("Name", style="white")
        table.add_column("Image", style="dim")
        table.add_column("Runtime", style="magenta")
        table.add_column("Status")
        table.add_column("IP", style="dim")
        for c in result.containers:
            sc = "green" if c.status == "running" else "red" if c.status == "stopped" else "yellow"
            table.add_row(
                c.container_id[:12], c.name, c.image[:40], c.runtime,
                f"[{sc}]{c.status}[/{sc}]",
                c.ip_address or "-",
            )
        console.print(table)

    # -- Threats table --
    if result.threats:
        console.print(f"\n[bold red]\U0001f6a8 Threats Found: {len(result.threats)}[/bold red]")
        tt = Table(title="Container Threats", box=box.ROUNDED, border_style="red")
        tt.add_column("Container", style="cyan")
        tt.add_column("Threat", style="white")
        tt.add_column("Type", style="magenta")
        tt.add_column("Severity")
        tt.add_column("Details", max_width=60)
        sev_colors = {"CRITICAL": "red", "HIGH": "yellow", "MEDIUM": "blue", "LOW": "green"}
        for t in result.threats:
            sc = sev_colors.get(t.severity, "white")
            tt.add_row(
                t.container_name, t.threat_name, t.threat_type,
                f"[{sc}]{t.severity}[/{sc}]",
                t.details[:60],
            )
        console.print(tt)
    else:
        console.print("\n[bold green]\u2705 No threats detected in containers.[/bold green]")

    if result.errors:
        console.print(f"\n[yellow]\u26a0 Errors: {len(result.errors)}[/yellow]")
        for err in result.errors:
            console.print(f"  [dim]\u2022 {err}[/dim]")

    console.print(
        f"\n[dim]Scan completed in {result.duration_seconds:.1f}s | "
        f"Containers scanned: {result.containers_scanned}/{result.containers_found}[/dim]"
    )


# ===================================================================
# update
# ===================================================================
@main.command()
@click.option("--force", is_flag=True, help="Force re-download even if signatures are fresh.")
@click.pass_obj
def update(cfg: Config, force: bool) -> None:
    """Update threat signatures from all configured feeds.

    Downloads the latest YARA rules, hash databases, and threat intelligence
    feeds. Requires network access and (optionally) API keys configured in
    .env or config.yaml.
    """
    from ayn_antivirus.signatures.manager import SignatureManager

    print_banner()

    console.print(
        Panel(
            "[bold]Updating threat signatures…[/bold]",
            title="[bold cyan]Signature Updater[/bold cyan]",
            border_style="cyan",
        )
    )

    mgr = SignatureManager(cfg)

    feed_names = mgr.feed_names
    feed_results = {}
    errors = []

    with make_progress() as progress:
        task = progress.add_task("Updating feeds…", total=len(feed_names))
        for name in feed_names:
            progress.update(task, description=f"Updating {name}…")
            try:
                stats = mgr.update_feed(name)
                feed_results[name] = stats
            except Exception as exc:
                feed_results[name] = {"error": str(exc)}
                errors.append(name)
            progress.advance(task)

    # --- Per-feed status table ---
    table = Table(
        title="Feed Update Results",
        box=box.ROUNDED,
        show_lines=True,
    )
    table.add_column("Feed", style="cyan")
    table.add_column("Status", width=10)
    table.add_column("Fetched", justify="right")
    table.add_column("Hashes", justify="right")
    table.add_column("IPs", justify="right")
    table.add_column("Domains", justify="right")
    table.add_column("URLs", justify="right")

    total_new = 0
    for name, stats in feed_results.items():
        if "error" in stats:
            table.add_row(name, "[red]ERROR[/red]", "-", "-", "-", "-", "-")
        else:
            inserted = stats.get("inserted", 0)
            total_new += inserted
            table.add_row(
                name,
                "[green]OK[/green]",
                str(stats.get("fetched", 0)),
                str(stats.get("hashes", 0)),
                str(stats.get("ips", 0)),
                str(stats.get("domains", 0)),
                str(stats.get("urls", 0)),
            )

    console.print()
    console.print(table)

    status_msg = (
        f"[green]All {len(feed_names)} feeds updated — {total_new} new entries.[/green]"
        if not errors
        else f"[yellow]{len(feed_names) - len(errors)}/{len(feed_names)} feeds updated, "
             f"{len(errors)} error(s).[/yellow]"
    )

    console.print(
        Panel(
            f"{status_msg}\n[bold]Database:[/bold] {cfg.db_path}",
            title="✅  [bold green]Update Complete[/bold green]" if not errors
                  else "⚠️  [bold yellow]Update Partial[/bold yellow]",
            border_style="green" if not errors else "yellow",
        )
    )

    mgr.close()


# ===================================================================
# quarantine  (sub-group)
# ===================================================================
@main.group()
@click.pass_obj
def quarantine(cfg: Config) -> None:
    """Manage the quarantine vault.

    Quarantined files are encrypted and isolated. Use subcommands to list,
    inspect, restore, or permanently delete quarantined items.
    """
    pass


def _get_vault(cfg: Config):
    """Lazily create a QuarantineVault from config."""
    from ayn_antivirus.quarantine.vault import QuarantineVault
    return QuarantineVault(cfg.quarantine_path)


@quarantine.command("list")
@click.pass_obj
def quarantine_list(cfg: Config) -> None:
    """List all quarantined items."""
    print_banner()

    vault = _get_vault(cfg)

    console.print(
        Panel(
            f"[bold]Quarantine path:[/bold] {cfg.quarantine_path}",
            title="[bold cyan]Quarantine Vault[/bold cyan]",
            border_style="cyan",
        )
    )

    items = vault.list_quarantined()
    if not items:
        console.print("[dim]Quarantine vault is empty.[/dim]")
        return

    table = Table(box=box.ROUNDED, show_lines=True)
    table.add_column("ID", style="dim", width=34)
    table.add_column("Threat", style="red")
    table.add_column("Original Path", style="cyan", max_width=50)
    table.add_column("Quarantined At", style="white")
    table.add_column("Size", style="dim", justify="right")

    for item in items:
        table.add_row(
            item.get("id", "?"),
            item.get("threat_name", "?"),
            item.get("original_path", "?"),
            item.get("quarantine_date", "?"),
            format_size(item.get("size", 0)),
        )

    console.print(table)
    console.print(f"\n[bold]{len(items)}[/bold] item(s) quarantined.")


@quarantine.command("restore")
@click.argument("quarantine_id", type=str)
@click.option("--output", type=click.Path(), default=None, help="Restore to this path instead of original.")
@click.pass_obj
def quarantine_restore(cfg: Config, quarantine_id: str, output: Optional[str]) -> None:
    """Restore a quarantined item by its ID.

    The file is decrypted and moved back to its original location.
    Use `quarantine list` to find the ID.
    """
    print_banner()

    vault = _get_vault(cfg)
    try:
        restored = vault.restore_file(quarantine_id, restore_path=output)
        console.print(f"[green]✅ Restored:[/green] {restored}")
    except FileNotFoundError as exc:
        console.print(f"[red]Error:[/red] {exc}")
        raise SystemExit(1)


@quarantine.command("delete")
@click.argument("quarantine_id", type=str)
@click.confirmation_option(prompt="Permanently delete this quarantined item?")
@click.pass_obj
def quarantine_delete(cfg: Config, quarantine_id: str) -> None:
    """Permanently delete a quarantined item by its ID.

    This action is irreversible. You will be prompted for confirmation.
    """
    print_banner()

    vault = _get_vault(cfg)
    if vault.delete_file(quarantine_id):
        console.print(f"[red]Deleted:[/red] {quarantine_id}")
    else:
        console.print(f"[yellow]Not found:[/yellow] {quarantine_id}")


@quarantine.command("info")
@click.argument("quarantine_id", type=str)
@click.pass_obj
def quarantine_info(cfg: Config, quarantine_id: str) -> None:
    """Show detailed information about a quarantined item."""
    print_banner()

    vault = _get_vault(cfg)
    try:
        info = vault.get_info(quarantine_id)
    except FileNotFoundError:
        console.print(f"[red]Error:[/red] Quarantine ID not found: {quarantine_id}")
        raise SystemExit(1)

    console.print(
        Panel(
            f"[bold]ID:[/bold]              {info.get('id', '?')}\n"
            f"[bold]Threat:[/bold]          {info.get('threat_name', '?')}\n"
            f"[bold]Type:[/bold]            {info.get('threat_type', '?')}\n"
            f"[bold]Severity:[/bold]        {info.get('severity', '?')}\n"
            f"[bold]Original path:[/bold]   {info.get('original_path', '?')}\n"
            f"[bold]Permissions:[/bold]     {info.get('original_permissions', '?')}\n"
            f"[bold]Size:[/bold]            {format_size(info.get('file_size', 0))}\n"
            f"[bold]Hash:[/bold]            {info.get('file_hash', '?')}\n"
            f"[bold]Quarantined:[/bold]     {info.get('quarantine_date', '?')}",
            title="[bold cyan]Quarantine Item Detail[/bold cyan]",
            border_style="cyan",
        )
    )


# ===================================================================
# monitor
# ===================================================================
@main.command()
@click.option(
    "--paths",
    multiple=True,
    help="Directories to watch (default: configured scan paths).",
)
@click.option("--daemon", "-d", is_flag=True, help="Run in background as a daemon.")
@click.pass_obj
def monitor(cfg: Config, paths: tuple, daemon: bool) -> None:
    """Start real-time file-system monitoring.

    Watches configured directories for new or modified files and scans them
    immediately. Uses inotify (Linux) / FSEvents (macOS) via watchdog.

    Press Ctrl+C to stop.
    """
    from ayn_antivirus.core.engine import ScanEngine
    from ayn_antivirus.monitor.realtime import RealtimeMonitor

    print_banner()

    watch_paths = list(paths) if paths else cfg.scan_paths

    console.print(
        Panel(
            "[bold]Watching:[/bold]  " + ", ".join(watch_paths) + "\n"
            "[bold]Mode:[/bold]      " + ("daemon" if daemon else "foreground") + "\n"
            "[bold]Auto-quarantine:[/bold] " + ("on" if cfg.auto_quarantine else "off"),
            title="[bold cyan]Real-Time Monitor[/bold cyan]",
            border_style="cyan",
        )
    )

    engine = ScanEngine(cfg)
    rt_monitor = RealtimeMonitor(cfg, engine)
    rt_monitor.start(paths=watch_paths, recursive=True)

    console.print("[green]\u2705 Real-time monitor active. Press Ctrl+C to stop.[/green]\n")
    try:
        while rt_monitor.is_running:
            time.sleep(1)
    except KeyboardInterrupt:
        rt_monitor.stop()
        console.print("\n[yellow]Monitor stopped.[/yellow]")


# ===================================================================
# dashboard
# ===================================================================
@main.command()
@click.option("--host", default=None, help="Dashboard host (default: 0.0.0.0).")
@click.option("--port", type=int, default=None, help="Dashboard port (default: 7777).")
@click.pass_obj
def dashboard(cfg: Config, host: Optional[str], port: Optional[int]) -> None:
    """Start the live web security dashboard.

    Opens an aiohttp web server with real-time system metrics, threat
    monitoring, container scanning, and signature management.

    \b
    Examples
    --------
      ayn-antivirus dashboard
      ayn-antivirus dashboard --host 127.0.0.1 --port 8080
    """
    print_banner()

    if host:
        cfg.dashboard_host = host
    if port:
        cfg.dashboard_port = port

    console.print(
        Panel(
            f"[bold cyan]\U0001f310 Starting AYN Antivirus Dashboard[/bold cyan]\n\n"
            f"  URL: [green]http://{cfg.dashboard_host}:{cfg.dashboard_port}[/green]\n"
            f"  Press [bold]Ctrl+C[/bold] to stop",
            title="\u2694\ufe0f  Dashboard",
            border_style="cyan",
        )
    )

    try:
        from ayn_antivirus.dashboard.server import DashboardServer

        server = DashboardServer(cfg)
        server.run()
    except KeyboardInterrupt:
        console.print("\n[yellow]Dashboard stopped.[/yellow]")
    except ImportError as exc:
        console.print(f"[red]Missing dependency: {exc}[/red]")
        console.print("[dim]Install aiohttp: pip install aiohttp[/dim]")
    except Exception as exc:
        console.print(f"[red]Dashboard error: {exc}[/red]")


# ===================================================================
# report
# ===================================================================
@main.command()
@click.option(
    "--format",
    "fmt",
    type=click.Choice(["text", "json", "html"], case_sensitive=False),
    default="text",
    show_default=True,
    help="Output format for the report.",
)
@click.option(
    "--output",
    "output_path",
    type=click.Path(dir_okay=False),
    default=None,
    help="Write report to this file instead of stdout.",
)
@click.option(
    "--path",
    "scan_path",
    type=click.Path(exists=True),
    default=None,
    help="Run a scan and generate a report from results.",
)
@click.pass_obj
def report(cfg: Config, fmt: str, output_path: Optional[str], scan_path: Optional[str]) -> None:
    """Generate a scan report.

    Runs a scan (or uses the last cached result) and compiles findings into
    a human- or machine-readable report.

    \b
    Examples
    --------
      ayn-antivirus report
      ayn-antivirus report --format json --output /tmp/report.json
      ayn-antivirus report --format html --output report.html
      ayn-antivirus report --path /var/www --format html --output www_report.html
    """
    from ayn_antivirus.core.engine import ScanEngine, ScanResult
    from ayn_antivirus.reports.generator import ReportGenerator

    print_banner()

    # Run a fresh scan to populate the report.
    engine = ScanEngine(cfg)
    if scan_path:
        console.print(f"[bold]Scanning:[/bold] {scan_path}")
        scan_result = engine.scan_path(scan_path, recursive=True)
    else:
        # Scan first configured path (or produce an empty result).
        target = cfg.scan_paths[0] if cfg.scan_paths else "/"
        if Path(target).exists():
            console.print(f"[bold]Scanning:[/bold] {target}")
            scan_result = engine.scan_path(target, recursive=True)
        else:
            scan_result = ScanResult()

    gen = ReportGenerator()

    if fmt == "json":
        content = gen.generate_json(scan_result)
    elif fmt == "html":
        content = gen.generate_html(scan_result)
    else:
        content = gen.generate_text(scan_result)

    if output_path:
        gen.save_report(content, output_path)
        console.print(f"[green]Report written to:[/green] {output_path}")
    else:
        out.print(content)


# ===================================================================
# status
# ===================================================================
@main.command()
@click.pass_obj
def status(cfg: Config) -> None:
    """Show current protection status.

    Displays last scan time, signature freshness, threat counts, quarantine
    size, and real-time monitor state at a glance.
    """
    print_banner()

    sig_db = Path(cfg.db_path)
    sig_status = "[green]up to date[/green]" if sig_db.exists() else "[red]not found[/red]"
    sig_modified = (
        datetime.fromtimestamp(sig_db.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
        if sig_db.exists()
        else "N/A"
    )

    # Use real vault count.
    try:
        vault = _get_vault(cfg)
        quarantine_count = vault.count()
    except Exception:
        quarantine_count = 0

    table = Table(box=box.SIMPLE_HEAVY, show_header=False, padding=(0, 2))
    table.add_column("Key", style="bold", width=24)
    table.add_column("Value")

    table.add_row("Version", __version__)
    table.add_row("Signature DB", sig_status)
    table.add_row("Signatures Updated", sig_modified)
    table.add_row("Last Scan", "[dim]N/A[/dim]")
    table.add_row("Threats (last scan)", "[green]0[/green]")
    table.add_row("Quarantined Items", str(quarantine_count))
    table.add_row(
        "Real-Time Monitor",
        "[green]active[/green]" if cfg.enable_realtime_monitor else "[dim]inactive[/dim]",
    )
    table.add_row("Auto-Quarantine", "[green]on[/green]" if cfg.auto_quarantine else "[dim]off[/dim]")
    table.add_row("YARA Engine", "[green]enabled[/green]" if cfg.enable_yara else "[dim]disabled[/dim]")
    table.add_row("Heuristics", "[green]enabled[/green]" if cfg.enable_heuristics else "[dim]disabled[/dim]")

    console.print(
        Panel(
            table,
            title="[bold cyan]Protection Status[/bold cyan]",
            border_style="cyan",
        )
    )


# ===================================================================
# config
# ===================================================================
@main.command("config")
@click.option("--show", is_flag=True, default=True, help="Display current configuration.")
@click.option("--set", "set_key", nargs=2, type=str, default=None, help="Set a config value: KEY VALUE.")
@click.pass_obj
def config_cmd(cfg: Config, show: bool, set_key: Optional[tuple]) -> None:
    """Show or edit the current configuration.

    Without flags, prints the active configuration as a table. Use --set to
    change a value (persisted to ~/.ayn-antivirus/config.yaml).

    \b
    Examples
    --------
      ayn-antivirus config
      ayn-antivirus config --set auto_quarantine true
      ayn-antivirus config --set scan_schedule '0 3 * * *'
    """
    print_banner()

    if set_key:
        key, value = set_key

        VALID_CONFIG_KEYS = {
            "scan_paths", "exclude_paths", "quarantine_path", "db_path",
            "log_path", "auto_quarantine", "scan_schedule", "max_file_size",
            "enable_yara", "enable_heuristics", "enable_realtime_monitor",
            "dashboard_host", "dashboard_port", "dashboard_db_path",
            "api_keys",
        }
        if key not in VALID_CONFIG_KEYS:
            console.print(f"[red]Invalid config key: {key}[/red]")
            console.print(f"[dim]Valid keys: {', '.join(sorted(VALID_CONFIG_KEYS))}[/dim]")
            return

        config_file = Path.home() / ".ayn-antivirus" / "config.yaml"
        config_file.parent.mkdir(parents=True, exist_ok=True)

        import yaml

        data = {}
        if config_file.exists():
            data = yaml.safe_load(config_file.read_text()) or {}

        # Coerce booleans / ints
        if value.lower() in ("true", "false"):
            value = value.lower() == "true"
        else:
            try:
                value = int(value)
            except ValueError:
                pass

        data[key] = value
        config_file.write_text(yaml.dump(data, default_flow_style=False))
        console.print(f"[green]Set[/green] [bold]{key}[/bold] = {value}")
        console.print(f"[dim]Saved to {config_file}[/dim]")
        return

    # Show current config
    table = Table(box=box.SIMPLE_HEAVY, show_header=False, padding=(0, 2))
    table.add_column("Key", style="bold", width=24)
    table.add_column("Value")

    table.add_row("scan_paths", ", ".join(cfg.scan_paths))
    table.add_row("exclude_paths", ", ".join(cfg.exclude_paths))
    table.add_row("quarantine_path", cfg.quarantine_path)
    table.add_row("db_path", cfg.db_path)
    table.add_row("log_path", cfg.log_path)
    table.add_row("auto_quarantine", str(cfg.auto_quarantine))
    table.add_row("scan_schedule", cfg.scan_schedule)
    table.add_row("max_file_size", format_size(cfg.max_file_size))
    table.add_row("enable_yara", str(cfg.enable_yara))
    table.add_row("enable_heuristics", str(cfg.enable_heuristics))
    table.add_row("enable_realtime_monitor", str(cfg.enable_realtime_monitor))
    table.add_row(
        "api_keys",
        ", ".join(f"{k}=***" for k in cfg.api_keys) if cfg.api_keys else "[dim](none)[/dim]",
    )

    console.print(
        Panel(
            table,
            title="[bold cyan]Active Configuration[/bold cyan]",
            border_style="cyan",
        )
    )


# ===================================================================
# fix
# ===================================================================
@main.command()
@click.option("--all", "fix_all", is_flag=True, help="Auto-remediate all detected threats.")
@click.option("--threat-id", type=int, default=None, help="Remediate a specific threat by ID.")
@click.option("--dry-run", is_flag=True, help="Preview actions without making changes.")
@click.pass_obj
def fix(cfg: Config, fix_all: bool, threat_id: Optional[int], dry_run: bool) -> None:
    """Auto-patch and remediate detected threats.

    Runs a quick scan to find threats, then applies automatic remediation:
    quarantine malicious files, kill rogue processes, remove malicious cron
    entries, and clean persistence mechanisms.

    \b
    Examples
    --------
      ayn-antivirus fix --all
      ayn-antivirus fix --all --dry-run
    """
    from ayn_antivirus.core.engine import ScanEngine
    from ayn_antivirus.remediation.patcher import AutoPatcher

    print_banner()

    if not fix_all and threat_id is None:
        console.print("[red]Error:[/red] Specify --all or --threat-id <ID>.")
        raise SystemExit(1)

    mode = "dry-run" if dry_run else "live"
    scope = f"threat #{threat_id}" if threat_id else "all threats"

    console.print(
        Panel(
            f"[bold]Mode:[/bold]   {mode}\n"
            f"[bold]Scope:[/bold]  {scope}",
            title="[bold cyan]Remediation Engine[/bold cyan]",
            border_style="cyan",
        )
    )

    # --- Run a quick scan to find threats ---
    engine = ScanEngine(cfg)

    console.print("\n[bold]Running quick scan to identify threats…[/bold]")
    scan_result = engine.quick_scan()

    threats = scan_result.threats
    if not threats:
        console.print(
            Panel(
                "[green]No threats found — nothing to remediate.[/green]",
                title="✅  [bold green]System Clean[/bold green]",
                border_style="green",
            )
        )
        return

    if threat_id is not None:
        if threat_id < 1 or threat_id > len(threats):
            console.print(f"[red]Error:[/red] Threat ID {threat_id} out of range (1-{len(threats)}).")
            raise SystemExit(1)
        threats = [threats[threat_id - 1]]

    # --- Remediate ---
    patcher = AutoPatcher(dry_run=dry_run)
    all_actions = []

    for threat in threats:
        threat_dict = {
            "threat_type": threat.threat_type.name if hasattr(threat.threat_type, "name") else str(threat.threat_type),
            "path": threat.path,
            "threat_name": threat.threat_name,
        }
        actions = patcher.remediate_threat(threat_dict)
        all_actions.extend(actions)

    if not all_actions:
        console.print("[green]No actionable remediation steps for found threats.[/green]")
        return

    # --- Display results ---
    table = Table(
        title="Remediation Actions" + (" (DRY RUN)" if dry_run else ""),
        box=box.ROUNDED,
        show_lines=True,
        title_style="bold yellow" if dry_run else "bold green",
    )
    table.add_column("#", style="dim", width=4)
    table.add_column("Action", style="white")
    table.add_column("Target", style="cyan", max_width=55)
    table.add_column("Status", width=10)
    table.add_column("Details", style="dim", max_width=40)

    for idx, action in enumerate(all_actions, 1):
        status_text = "[green]done[/green]" if action.success else "[red]failed[/red]"
        if action.dry_run:
            status_text = "[dim]pending[/dim]"
        table.add_row(
            str(idx),
            action.action,
            action.target,
            status_text,
            action.details[:40] if action.details else "",
        )

    console.print()
    console.print(table)

    if dry_run:
        console.print("\n[yellow]Dry run — no changes were made.[/yellow]")
    else:
        succeeded = sum(1 for a in all_actions if a.success)
        console.print(
            f"\n[green]✅ {succeeded}/{len(all_actions)} remediation action(s) applied.[/green]"
        )
