"""
Novyx CLI - Command-line interface for Novyx Core API.

Manage memory, rollback, audit trails, and traces from the terminal.

Usage:
    novyx status                              # Show API health and usage
    novyx memories list                       # List all memories
    novyx memories search "user preferences"  # Semantic search
    novyx rollback "2 hours ago"             # Time-travel with preview
    novyx audit list --limit 50              # View audit trail
    novyx traces list                        # List trace sessions (Pro+)
"""

import json
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional, Dict, Any

import click

# Rich imports (optional dependency)
try:
    from rich.console import Console
    from rich.table import Table
    from rich.panel import Panel
    from rich.progress import Progress, SpinnerColumn, TextColumn
    from rich.prompt import Confirm
    from rich import box
    RICH_AVAILABLE = True
except ImportError:
    RICH_AVAILABLE = False
    Console = None

from novyx import Novyx, NovyxAuthError, NovyxForbiddenError, NovyxRateLimitError, NovyxError

# =============================================================================
# Configuration Management
# =============================================================================

CONFIG_DIR = Path.home() / ".novyx"
CONFIG_FILE = CONFIG_DIR / "config.json"


def load_config() -> Dict[str, Any]:
    """Load configuration from ~/.novyx/config.json."""
    if not CONFIG_FILE.exists():
        return {}

    try:
        with open(CONFIG_FILE) as f:
            return json.load(f)
    except Exception:
        return {}


def save_config(config: Dict[str, Any]) -> None:
    """Save configuration to ~/.novyx/config.json."""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)

    with open(CONFIG_FILE, "w") as f:
        json.dump(config, f, indent=2)


def get_api_key(ctx_api_key: Optional[str]) -> str:
    """
    Get API key with priority order:
    1. --api-key flag
    2. NOVYX_API_KEY env var
    3. ~/.novyx/config.json
    """
    # 1. Command-line flag
    if ctx_api_key:
        return ctx_api_key

    # 2. Environment variable
    env_key = os.environ.get("NOVYX_API_KEY")
    if env_key:
        return env_key

    # 3. Config file
    config = load_config()
    config_key = config.get("api_key")
    if config_key:
        return config_key

    # No API key found
    error_msg = (
        "No API key found. Please provide one via:\n"
        "  1. --api-key flag\n"
        "  2. NOVYX_API_KEY environment variable\n"
        "  3. novyx config set api_key <key>"
    )

    if RICH_AVAILABLE:
        console = Console()
        console.print(f"[bold red]Error:[/bold red] {error_msg}")
    else:
        click.echo(f"Error: {error_msg}", err=True)

    sys.exit(1)


def get_client(ctx_api_key: Optional[str]) -> Novyx:
    """Get initialized Novyx client."""
    api_key = get_api_key(ctx_api_key)
    return Novyx(api_key=api_key)


# =============================================================================
# Rich Console Helpers
# =============================================================================

def get_console() -> Console:
    """Get Rich console or fallback."""
    if RICH_AVAILABLE:
        return Console()
    return None


def print_table(data: list, columns: list, title: str = None) -> None:
    """Print data as a table using Rich or plain text."""
    if not data:
        if RICH_AVAILABLE:
            console = get_console()
            console.print(f"[yellow]No data to display[/yellow]")
        else:
            click.echo("No data to display")
        return

    if RICH_AVAILABLE:
        console = get_console()
        table = Table(title=title, box=box.ROUNDED)

        for col in columns:
            table.add_column(col["header"], style=col.get("style"))

        for row in data:
            table.add_row(*[str(row.get(col["key"], "")) for col in columns])

        console.print(table)
    else:
        # Fallback to plain text
        if title:
            click.echo(f"\n{title}\n{'=' * len(title)}")

        # Print header
        headers = [col["header"] for col in columns]
        click.echo("\t".join(headers))
        click.echo("\t".join(["-" * len(h) for h in headers]))

        # Print rows
        for row in data:
            values = [str(row.get(col["key"], "")) for col in columns]
            click.echo("\t".join(values))


def print_error(message: str) -> None:
    """Print error message."""
    if RICH_AVAILABLE:
        console = get_console()
        console.print(f"[bold red]Error:[/bold red] {message}")
    else:
        click.echo(f"Error: {message}", err=True)


def print_success(message: str) -> None:
    """Print success message."""
    if RICH_AVAILABLE:
        console = get_console()
        console.print(f"[bold green]✓[/bold green] {message}")
    else:
        click.echo(f"✓ {message}")


def print_warning(message: str) -> None:
    """Print warning message."""
    if RICH_AVAILABLE:
        console = get_console()
        console.print(f"[bold yellow]⚠[/bold yellow] {message}")
    else:
        click.echo(f"⚠ {message}")


def print_info(message: str) -> None:
    """Print info message."""
    if RICH_AVAILABLE:
        console = get_console()
        console.print(f"[bold blue]ℹ[/bold blue] {message}")
    else:
        click.echo(f"ℹ {message}")


def print_limit_error(error) -> None:
    """
    Print rate limit error with rich formatting.

    Args:
        error: NovyxRateLimitError exception instance
    """
    if RICH_AVAILABLE:
        from rich.panel import Panel

        # Build content lines
        lines = []
        message = error.args[0] if error.args else str(error)
        lines.append(message)
        lines.append("")

        # Usage info with color coding
        if error.current is not None and error.limit is not None:
            lines.append(f"Usage: [green]{error.current:,}[/green] / [yellow]{error.limit:,}[/yellow]")

        # Plan info
        if error.plan:
            lines.append(f"Plan: {error.plan}")

        # Reset time
        if error.resets_at:
            lines.append(f"Resets: {error.resets_at}")

        # Helpful tip
        if error.tip:
            lines.append("")
            lines.append(f"💡 Tip: {error.tip}")

        # Upgrade link
        if error.upgrade_url:
            lines.append("")
            lines.append(f"🔗 Upgrade: [blue]{error.upgrade_url}[/blue]")

        console = get_console()
        console.print(Panel(
            "\n".join(lines),
            title="[bold red]Rate Limit[/bold red]",
            border_style="red"
        ))
    else:
        # Fallback to plain text
        print_error(str(error))
        if hasattr(error, 'current') and error.current and hasattr(error, 'limit') and error.limit:
            click.echo(f"  Usage: {error.current:,} / {error.limit:,}", err=True)
        if hasattr(error, 'plan') and error.plan:
            click.echo(f"  Plan: {error.plan}", err=True)
        if hasattr(error, 'resets_at') and error.resets_at:
            click.echo(f"  Resets: {error.resets_at}", err=True)
        if hasattr(error, 'tip') and error.tip:
            click.echo(f"  Tip: {error.tip}", err=True)
        if hasattr(error, 'upgrade_url') and error.upgrade_url:
            click.echo(f"  Upgrade: {error.upgrade_url}", err=True)


def print_feature_error(error) -> None:
    """
    Print feature gate error with rich formatting.

    Args:
        error: NovyxForbiddenError exception instance
    """
    if RICH_AVAILABLE:
        from rich.panel import Panel

        # Build content lines
        lines = []
        message = error.args[0] if error.args else str(error)
        lines.append(message)
        lines.append("")

        # Feature info
        if error.feature:
            lines.append(f"Feature: {error.feature}")

        # Required plan
        if hasattr(error, 'required_plan') and error.required_plan:
            lines.append(f"Required: {error.required_plan}")

        # Current plan
        if error.plan:
            lines.append(f"Current plan: {error.plan}")

        # Upgrade link
        if error.upgrade_url:
            lines.append("")
            lines.append(f"🔗 Upgrade: [blue]{error.upgrade_url}[/blue]")

        console = get_console()
        console.print(Panel(
            "\n".join(lines),
            title="[bold yellow]Upgrade Required[/bold yellow]",
            border_style="yellow"
        ))
    else:
        # Fallback to plain text
        print_error(str(error))
        if hasattr(error, 'feature') and error.feature:
            click.echo(f"  Feature: {error.feature}", err=True)
        if hasattr(error, 'required_plan') and error.required_plan:
            click.echo(f"  Required: {error.required_plan}", err=True)
        elif hasattr(error, 'tier_required') and error.tier_required:
            click.echo(f"  Required: {error.tier_required}", err=True)
        if hasattr(error, 'plan') and error.plan:
            click.echo(f"  Current plan: {error.plan}", err=True)
        if hasattr(error, 'upgrade_url') and error.upgrade_url:
            click.echo(f"  Upgrade: {error.upgrade_url}", err=True)


def confirm_action(message: str, default: bool = False) -> bool:
    """Prompt for confirmation."""
    if RICH_AVAILABLE:
        return Confirm.ask(message, default=default)
    else:
        return click.confirm(message, default=default)


# =============================================================================
# Main CLI Group
# =============================================================================

@click.group()
@click.option("--api-key", envvar="NOVYX_API_KEY", help="Novyx API key")
@click.pass_context
def cli(ctx, api_key):
    """
    Novyx CLI - Manage memory, rollback, audit, and traces.

    \b
    Examples:
        novyx status                              # Show API health and usage
        novyx memories list                       # List all memories
        novyx memories search "user preferences"  # Semantic search
        novyx rollback "2 hours ago"             # Time-travel with preview
        novyx audit list --limit 50              # View audit trail
        novyx traces list                        # List trace sessions (Pro+)

    \b
    Configuration:
        API key resolution (priority order):
        1. --api-key flag
        2. NOVYX_API_KEY environment variable
        3. ~/.novyx/config.json

        Set API key: novyx config set api_key <key>
    """
    ctx.ensure_object(dict)
    ctx.obj["api_key"] = api_key


# =============================================================================
# Memory Commands
# =============================================================================

@cli.group()
def memories():
    """Manage memories (list, search, delete)."""
    pass


@memories.command("list")
@click.option("--limit", default=100, help="Maximum memories to retrieve")
@click.option("--format", type=click.Choice(["table", "json"]), default="table", help="Output format")
@click.option("--tags", help="Filter by tags (comma-separated)")
@click.pass_context
def memories_list(ctx, limit, format, tags):
    """List all memories."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        tag_list = tags.split(",") if tags else None

        if RICH_AVAILABLE and format == "table":
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task("Fetching memories...", total=None)
                result = client.memories(limit=limit, tags=tag_list)
        else:
            result = client.memories(limit=limit, tags=tag_list)

        if format == "json":
            click.echo(json.dumps(result, indent=2))
        else:
            # Table format
            data = []
            for mem in result:
                data.append({
                    "uuid": mem.get("uuid", "")[:8] + "...",
                    "observation": mem.get("observation", "")[:50] + ("..." if len(mem.get("observation", "")) > 50 else ""),
                    "importance": mem.get("importance", 5),
                    "tags": ", ".join(mem.get("tags", [])[:3]),
                    "created": mem.get("created_at", "")[:10],
                })

            columns = [
                {"header": "UUID", "key": "uuid", "style": "cyan"},
                {"header": "Observation", "key": "observation", "style": "white"},
                {"header": "Importance", "key": "importance", "style": "yellow"},
                {"header": "Tags", "key": "tags", "style": "green"},
                {"header": "Created", "key": "created", "style": "blue"},
            ]

            print_table(data, columns, title=f"Memories (showing {len(result)} of {limit})")

            print_info(f"Total memories retrieved: {len(result)}")

    except NovyxAuthError:
        print_error("Invalid API key")
        sys.exit(1)
    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


@memories.command("search")
@click.argument("query")
@click.option("--limit", default=10, help="Maximum results")
@click.option("--min-score", default=0.0, help="Minimum relevance score (0-1)")
@click.option("--format", type=click.Choice(["table", "json"]), default="table", help="Output format")
@click.pass_context
def memories_search(ctx, query, limit, min_score, format):
    """Semantic search for memories."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        if RICH_AVAILABLE and format == "table":
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task(f"Searching for '{query}'...", total=None)
                results = client.recall(query=query, limit=limit, min_score=min_score)
        else:
            results = client.recall(query=query, limit=limit, min_score=min_score)

        if format == "json":
            click.echo(json.dumps(results, indent=2))
        else:
            # Table format
            data = []
            for mem in results:
                data.append({
                    "score": f"{mem.get('score', 0):.3f}",
                    "observation": mem.get("observation", "")[:60] + ("..." if len(mem.get("observation", "")) > 60 else ""),
                    "uuid": mem.get("uuid", "")[:8] + "...",
                    "importance": mem.get("importance", 5),
                })

            columns = [
                {"header": "Score", "key": "score", "style": "bold green"},
                {"header": "Observation", "key": "observation", "style": "white"},
                {"header": "UUID", "key": "uuid", "style": "cyan"},
                {"header": "Importance", "key": "importance", "style": "yellow"},
            ]

            print_table(data, columns, title=f"Search Results for '{query}'")

            if not results:
                print_warning(f"No memories found matching '{query}'")
            else:
                print_info(f"Found {len(results)} matching memories")

    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


@memories.command("count")
@click.pass_context
def memories_count(ctx):
    """Get total memory count."""
    try:
        client = get_client(ctx.obj.get("api_key"))
        stats = client.stats()

        total = stats.get("total_memories", 0)

        if RICH_AVAILABLE:
            console = get_console()
            console.print(Panel(
                f"[bold cyan]{total:,}[/bold cyan]",
                title="Total Memories",
                border_style="cyan"
            ))
        else:
            click.echo(f"Total memories: {total:,}")

    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


@memories.command("delete")
@click.argument("memory_id")
@click.option("--yes", is_flag=True, help="Skip confirmation")
@click.pass_context
def memories_delete(ctx, memory_id, yes):
    """Delete a memory by ID."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        # Confirm deletion
        if not yes:
            if not confirm_action(f"Delete memory {memory_id}?", default=False):
                print_info("Cancelled")
                return

        success = client.forget(memory_id)

        if success:
            print_success(f"Memory {memory_id} deleted successfully")
        else:
            print_error(f"Failed to delete memory {memory_id}")
            sys.exit(1)

    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


# =============================================================================
# Rollback Commands
# =============================================================================

@cli.group()
def rollback():
    """Rollback operations (time-travel debugging)."""
    pass


@rollback.command("preview")
@click.argument("target")
@click.pass_context
def rollback_preview(ctx, target):
    """Preview rollback changes without executing."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        if RICH_AVAILABLE:
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task(f"Previewing rollback to '{target}'...", total=None)
                preview = client.rollback_preview(target=target)
        else:
            preview = client.rollback_preview(target=target)

        if RICH_AVAILABLE:
            console = get_console()

            # Create preview table
            table = Table(title=f"Rollback Preview: {target}", box=box.DOUBLE)
            table.add_column("Metric", style="cyan")
            table.add_column("Value", style="yellow")

            table.add_row("Target Timestamp", preview.get("target_timestamp", ""))
            table.add_row("Artifacts Modified", str(preview.get("artifacts_modified", 0)))
            table.add_row("Artifacts Deleted", str(preview.get("artifacts_deleted", 0)))
            table.add_row("Safe Rollback", "✓ Yes" if preview.get("safe_rollback") else "✗ No")

            console.print(table)

            # Show warnings if any
            warnings = preview.get("warnings", [])
            if warnings:
                console.print("\n[bold yellow]Warnings:[/bold yellow]")
                for warning in warnings:
                    console.print(f"  • {warning}")

        else:
            click.echo(f"\nRollback Preview: {target}")
            click.echo("=" * 50)
            click.echo(f"Target Timestamp: {preview.get('target_timestamp', '')}")
            click.echo(f"Artifacts Modified: {preview.get('artifacts_modified', 0)}")
            click.echo(f"Artifacts Deleted: {preview.get('artifacts_deleted', 0)}")
            click.echo(f"Safe Rollback: {'Yes' if preview.get('safe_rollback') else 'No'}")

            warnings = preview.get("warnings", [])
            if warnings:
                click.echo("\nWarnings:")
                for warning in warnings:
                    click.echo(f"  • {warning}")

    except NovyxForbiddenError as e:
        print_feature_error(e)
        sys.exit(1)
    except NovyxRateLimitError as e:
        print_limit_error(e)
        sys.exit(1)
    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


@rollback.command("execute")
@click.argument("target")
@click.option("--yes", is_flag=True, help="Skip confirmation")
@click.pass_context
def rollback_execute(ctx, target, yes):
    """Execute rollback to specified time (SHOWS PREVIEW FIRST)."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        # ALWAYS show preview first
        if RICH_AVAILABLE:
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task(f"Previewing rollback to '{target}'...", total=None)
                preview = client.rollback_preview(target=target)
        else:
            click.echo("Fetching rollback preview...")
            preview = client.rollback_preview(target=target)

        # Show preview
        if RICH_AVAILABLE:
            console = get_console()
            table = Table(title=f"Rollback Preview: {target}", box=box.DOUBLE)
            table.add_column("Metric", style="cyan")
            table.add_column("Value", style="yellow")

            table.add_row("Target Timestamp", preview.get("target_timestamp", ""))
            table.add_row("Artifacts Modified", str(preview.get("artifacts_modified", 0)))
            table.add_row("Artifacts Deleted", str(preview.get("artifacts_deleted", 0)))
            table.add_row("Safe Rollback", "✓ Yes" if preview.get("safe_rollback") else "✗ No")

            console.print(table)

            warnings = preview.get("warnings", [])
            if warnings:
                console.print("\n[bold yellow]Warnings:[/bold yellow]")
                for warning in warnings:
                    console.print(f"  • {warning}")

        else:
            click.echo(f"\nRollback Preview: {target}")
            click.echo("=" * 50)
            click.echo(f"Target Timestamp: {preview.get('target_timestamp', '')}")
            click.echo(f"Artifacts Modified: {preview.get('artifacts_modified', 0)}")
            click.echo(f"Artifacts Deleted: {preview.get('artifacts_deleted', 0)}")

        # Confirm execution
        if not yes:
            if not confirm_action(f"\nExecute rollback to '{target}'?", default=False):
                print_info("Rollback cancelled")
                return

        # Execute rollback
        if RICH_AVAILABLE:
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task("Executing rollback...", total=None)
                result = client.rollback(target=target, dry_run=False)
        else:
            click.echo("Executing rollback...")
            result = client.rollback(target=target, dry_run=False)

        if result.get("success"):
            print_success(f"Rollback completed successfully")

            if RICH_AVAILABLE:
                console.print(f"  Rolled back to: [cyan]{result.get('rolled_back_to')}[/cyan]")
                console.print(f"  Artifacts restored: [yellow]{result.get('artifacts_restored', 0)}[/yellow]")
                console.print(f"  Operations undone: [yellow]{result.get('operations_undone', 0)}[/yellow]")
            else:
                click.echo(f"  Rolled back to: {result.get('rolled_back_to')}")
                click.echo(f"  Artifacts restored: {result.get('artifacts_restored', 0)}")
                click.echo(f"  Operations undone: {result.get('operations_undone', 0)}")
        else:
            print_error("Rollback failed")
            sys.exit(1)

    except NovyxForbiddenError as e:
        print_feature_error(e)
        sys.exit(1)
    except NovyxRateLimitError as e:
        print_limit_error(e)
        sys.exit(1)
    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


@rollback.command("history")
@click.option("--limit", default=10, help="Maximum rollbacks to show")
@click.pass_context
def rollback_history(ctx, limit):
    """Show past rollback operations."""
    try:
        client = get_client(ctx.obj.get("api_key"))
        history = client.rollback_history(limit=limit)

        if not history:
            print_warning("No rollback history found")
            return

        data = []
        for rb in history:
            data.append({
                "timestamp": rb.get("executed_at", "")[:19],
                "target": rb.get("target_timestamp", "")[:19],
                "restored": rb.get("artifacts_restored", 0),
                "undone": rb.get("operations_undone", 0),
            })

        columns = [
            {"header": "Executed At", "key": "timestamp", "style": "cyan"},
            {"header": "Target Time", "key": "target", "style": "yellow"},
            {"header": "Restored", "key": "restored", "style": "green"},
            {"header": "Undone", "key": "undone", "style": "red"},
        ]

        print_table(data, columns, title=f"Rollback History (last {len(history)})")

    except NovyxForbiddenError as e:
        print_feature_error(e)
        sys.exit(1)
    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


# Make "novyx rollback <target>" work directly
@cli.command("rollback")
@click.argument("target")
@click.option("--yes", is_flag=True, help="Skip confirmation")
@click.pass_context
def rollback_shortcut(ctx, target, yes):
    """Execute rollback to specified time (SHOWS PREVIEW FIRST)."""
    ctx.forward(rollback_execute)


# =============================================================================
# Audit Commands
# =============================================================================

@cli.group()
def audit():
    """Audit trail operations (list, export, verify)."""
    pass


@audit.command("list")
@click.option("--limit", default=50, help="Maximum entries to retrieve")
@click.option("--operation", help="Filter by operation (CREATE, UPDATE, DELETE, ROLLBACK)")
@click.option("--format", type=click.Choice(["table", "json"]), default="table", help="Output format")
@click.pass_context
def audit_list(ctx, limit, operation, format):
    """List audit trail entries."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        if RICH_AVAILABLE and format == "table":
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task("Fetching audit entries...", total=None)
                entries = client.audit(limit=limit, operation=operation)
        else:
            entries = client.audit(limit=limit, operation=operation)

        if format == "json":
            click.echo(json.dumps(entries, indent=2))
        else:
            # Table format
            data = []
            for entry in entries:
                data.append({
                    "timestamp": entry.get("timestamp", "")[:19],
                    "operation": entry.get("operation", ""),
                    "artifact": entry.get("artifact_id", "")[:12] + "...",
                    "agent": entry.get("agent_id", "")[:15],
                    "hash": entry.get("content_hash", "")[:12] + "...",
                })

            columns = [
                {"header": "Timestamp", "key": "timestamp", "style": "cyan"},
                {"header": "Operation", "key": "operation", "style": "yellow"},
                {"header": "Artifact ID", "key": "artifact", "style": "green"},
                {"header": "Agent", "key": "agent", "style": "blue"},
                {"header": "Content Hash", "key": "hash", "style": "white"},
            ]

            print_table(data, columns, title=f"Audit Trail (last {len(entries)} entries)")

    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


@audit.command("export")
@click.option("--format", type=click.Choice(["csv", "json", "jsonl"]), default="csv", help="Export format")
@click.pass_context
def audit_export(ctx, format):
    """Export audit log (Pro+ only). Outputs to stdout, redirect to file."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        if RICH_AVAILABLE:
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task("Exporting audit log...", total=None)
                data = client.audit_export(format=format)
        else:
            data = client.audit_export(format=format)

        # Output raw bytes to stdout
        sys.stdout.buffer.write(data)

    except NovyxForbiddenError as e:
        print_feature_error(e)
        sys.exit(1)
    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


@audit.command("verify")
@click.pass_context
def audit_verify(ctx):
    """Verify audit trail integrity chain."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        if RICH_AVAILABLE:
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task("Verifying audit integrity...", total=None)
                result = client.audit_verify()
        else:
            click.echo("Verifying audit integrity...")
            result = client.audit_verify()

        valid = result.get("valid", False)

        if RICH_AVAILABLE:
            console = get_console()

            if valid:
                console.print(Panel(
                    "[bold green]✓ Audit trail integrity verified[/bold green]\n\n"
                    f"Total entries: [cyan]{result.get('total_entries', 0):,}[/cyan]\n"
                    f"Chain head: [yellow]{result.get('chain_head', '')[:16]}...[/yellow]",
                    title="Audit Verification",
                    border_style="green"
                ))
            else:
                console.print(Panel(
                    "[bold red]✗ Integrity check failed[/bold red]\n\n"
                    f"Errors found: [red]{len(result.get('errors', []))}[/red]",
                    title="Audit Verification",
                    border_style="red"
                ))

                if result.get("errors"):
                    console.print("\n[bold red]Errors:[/bold red]")
                    for error in result.get("errors", []):
                        console.print(f"  • {error}")

        else:
            if valid:
                click.echo("\n✓ Audit trail integrity verified")
                click.echo(f"Total entries: {result.get('total_entries', 0):,}")
                click.echo(f"Chain head: {result.get('chain_head', '')[:16]}...")
            else:
                click.echo("\n✗ Integrity check failed")
                click.echo(f"Errors found: {len(result.get('errors', []))}")

                if result.get("errors"):
                    click.echo("\nErrors:")
                    for error in result.get("errors", []):
                        click.echo(f"  • {error}")

        sys.exit(0 if valid else 1)

    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


# =============================================================================
# Trace Commands (Pro+ only)
# =============================================================================

@cli.group()
def traces():
    """Trace audit operations (Pro+ only)."""
    pass


@traces.command("list")
@click.option("--limit", default=20, help="Maximum traces to retrieve")
@click.pass_context
def traces_list(ctx, limit):
    """List trace sessions (Pro+ only)."""
    print_error("Trace list endpoint not yet implemented in SDK")
    print_info("Coming soon in Novyx Core v2.1")
    sys.exit(1)


@traces.command("show")
@click.argument("trace_id")
@click.pass_context
def traces_show(ctx, trace_id):
    """Show trace steps (Pro+ only)."""
    print_error("Trace show endpoint not yet implemented in SDK")
    print_info("Coming soon in Novyx Core v2.1")
    sys.exit(1)


@traces.command("verify")
@click.argument("trace_id")
@click.pass_context
def traces_verify(ctx, trace_id):
    """Verify trace integrity (Pro+ only)."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        if RICH_AVAILABLE:
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                progress.add_task(f"Verifying trace {trace_id}...", total=None)
                result = client.trace_verify(trace_id=trace_id)
        else:
            click.echo(f"Verifying trace {trace_id}...")
            result = client.trace_verify(trace_id=trace_id)

        valid = result.get("valid", False)

        if RICH_AVAILABLE:
            console = get_console()

            if valid:
                console.print(Panel(
                    f"[bold green]✓ Trace integrity verified[/bold green]\n\n"
                    f"Trace ID: [cyan]{trace_id}[/cyan]\n"
                    f"Steps verified: [yellow]{result.get('steps_verified', 0)}[/yellow]",
                    title="Trace Verification",
                    border_style="green"
                ))
            else:
                console.print(Panel(
                    f"[bold red]✗ Trace integrity check failed[/bold red]\n\n"
                    f"Trace ID: [cyan]{trace_id}[/cyan]\n"
                    f"Errors: [red]{len(result.get('errors', []))}[/red]",
                    title="Trace Verification",
                    border_style="red"
                ))

        else:
            if valid:
                click.echo(f"\n✓ Trace {trace_id} integrity verified")
                click.echo(f"Steps verified: {result.get('steps_verified', 0)}")
            else:
                click.echo(f"\n✗ Trace {trace_id} integrity check failed")

        sys.exit(0 if valid else 1)

    except NovyxForbiddenError as e:
        print_feature_error(e)
        sys.exit(1)
    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


# =============================================================================
# Status Command
# =============================================================================

@cli.command()
@click.pass_context
def status(ctx):
    """Show API health, current plan, and usage statistics."""
    try:
        client = get_client(ctx.obj.get("api_key"))

        if RICH_AVAILABLE:
            console = get_console()
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                console=console,
            ) as progress:
                task1 = progress.add_task("Checking API health...", total=None)
                health = client.health()

                progress.update(task1, description="Fetching usage stats...")
                usage = client.usage()

                progress.remove_task(task1)

        else:
            click.echo("Checking API health...")
            health = client.health()
            click.echo("Fetching usage stats...")
            usage = client.usage()

        # Display status
        if RICH_AVAILABLE:
            console = get_console()

            # Health panel
            health_status = health.get("status", "unknown")
            health_color = "green" if health_status == "healthy" else "red"

            console.print(Panel(
                f"[bold {health_color}]{health_status.upper()}[/bold {health_color}]\n"
                f"Version: [cyan]{health.get('version', 'unknown')}[/cyan]",
                title="API Health",
                border_style=health_color
            ))

            # Plan & Usage
            tier = usage.get("tier", "unknown").upper()
            tier_color = {"FREE": "yellow", "STARTER": "green", "PRO": "blue", "ENTERPRISE": "magenta"}.get(tier, "white")

            console.print(Panel(
                f"[bold {tier_color}]{tier} TIER[/bold {tier_color}]",
                title="Current Plan",
                border_style=tier_color
            ))

            # Pressure-colored status indicator
            def _status_indicator(pct):
                if pct < 50:
                    return "[green]✓[/green]"
                if pct < 80:
                    return "[yellow]✓[/yellow]"
                if pct < 95:
                    return "[yellow]⚠[/yellow]"
                return "[red]✗[/red]"

            # Usage table
            table = Table(title="Usage Statistics", box=box.ROUNDED)
            table.add_column("Resource", style="cyan")
            table.add_column("Current", style="yellow")
            table.add_column("Limit", style="green")
            table.add_column("Status", style="white")

            # API calls
            api_calls = usage.get("api_calls", {})
            current_calls = api_calls.get("current", 0)
            limit_calls = api_calls.get("limit")
            unlimited_calls = api_calls.get("unlimited", False)

            if unlimited_calls:
                table.add_row("API Calls", f"{current_calls:,}", "Unlimited", "✓")
            else:
                percent = (current_calls / limit_calls * 100) if limit_calls else 0
                table.add_row("API Calls", f"{current_calls:,}", f"{limit_calls:,}", _status_indicator(percent))

            # Rollbacks
            rollbacks = usage.get("rollbacks", {})
            current_rb = rollbacks.get("current", 0)
            limit_rb = rollbacks.get("limit")
            unlimited_rb = rollbacks.get("unlimited", False)

            if unlimited_rb:
                table.add_row("Rollbacks", f"{current_rb:,}", "Unlimited", "✓")
            else:
                percent_rb = (current_rb / limit_rb * 100) if limit_rb else 0
                table.add_row("Rollbacks", f"{current_rb:,}", f"{limit_rb:,}", _status_indicator(percent_rb))

            # Memories
            memories = usage.get("memories", {})
            current_mem = memories.get("current", 0)
            limit_mem = memories.get("limit")
            unlimited_mem = memories.get("unlimited", False)

            if unlimited_mem:
                table.add_row("Memories", f"{current_mem:,}", "Unlimited", "✓")
            else:
                percent_mem = (current_mem / limit_mem * 100) if limit_mem else 0
                table.add_row("Memories", f"{current_mem:,}", f"{limit_mem:,}", _status_indicator(percent_mem))

            console.print(table)

            # Phase B-1: Cost tracking (visibility only)
            pressure_level = usage.get("usage_pressure_level", "low")
            pressure_colors = {
                "low": "green", "medium": "yellow",
                "high": "red", "critical": "bold red",
            }
            p_color = pressure_colors.get(pressure_level, "white")
            spend = usage.get("spend_estimate")
            projected_date = usage.get("projected_limit_date")
            budget_alert_flag = usage.get("budget_alert", False)
            upgrade_msg = usage.get("upgrade_message")

            if pressure_level != "low" or spend:
                console.print(
                    f"\n[bold]Usage Pressure:[/bold] "
                    f"[{p_color}]{pressure_level.upper()}[/{p_color}]"
                )

                quota_pct = usage.get("quota_percent", {})
                if quota_pct:
                    parts = []
                    for resource, pct in quota_pct.items():
                        label = resource.replace("_", " ").title()
                        parts.append(f"{label}: {pct:.0f}%")
                    console.print(f"  Quota: {' | '.join(parts)}")

            if spend:
                console.print(f"\n[bold]Estimated Spend (this month):[/bold]")
                console.print(
                    f"  Current: [cyan]"
                    f"${spend.get('spend_estimate_usd', 0):.4f}[/cyan]"
                )
                console.print(
                    f"  Projected: [cyan]"
                    f"${spend.get('projected_spend_estimate_usd', 0):.4f}[/cyan]"
                )
                storage_cost = spend.get("storage_estimate_usd", 0)
                if storage_cost > 0:
                    console.print(
                        f"  Storage: [cyan]${storage_cost:.4f}[/cyan]"
                    )

            if projected_date:
                console.print(
                    f"\n  [yellow]⚠ Projected to hit limit by "
                    f"{projected_date}[/yellow]"
                )

            if budget_alert_flag:
                console.print(Panel(
                    "[bold yellow]Budget Alert[/bold yellow]\n"
                    "Your current or projected spend is approaching "
                    "your budget limit.",
                    border_style="yellow",
                ))

            if upgrade_msg:
                console.print(Panel(
                    f"[bold]{upgrade_msg}[/bold]",
                    title="Upgrade Available",
                    border_style="cyan",
                ))

            # Features
            features = usage.get("features", {})
            audit_retention = usage.get("audit_retention_days", 0)

            console.print(f"\n[bold]Features:[/bold]")
            console.print(f"  Audit retention: [cyan]{audit_retention} days[/cyan]")
            console.print(f"  Anomaly alerts: [{'green' if features.get('anomaly_alerts') else 'red'}]{'✓' if features.get('anomaly_alerts') else '✗'}[/]")
            console.print(f"  Trace audit: [{'green' if features.get('trace_audit') else 'red'}]{'✓' if features.get('trace_audit') else '✗'}[/]")
            console.print(f"  Priority support: [{'green' if features.get('priority_support') else 'red'}]{'✓' if features.get('priority_support') else '✗'}[/]")

        else:
            # Plain text output
            click.echo("\n=== API Health ===")
            click.echo(f"Status: {health.get('status', 'unknown').upper()}")
            click.echo(f"Version: {health.get('version', 'unknown')}")

            click.echo("\n=== Current Plan ===")
            click.echo(f"Tier: {usage.get('tier', 'unknown').upper()}")

            click.echo("\n=== Usage Statistics ===")
            api_calls = usage.get("api_calls", {})
            click.echo(f"API Calls: {api_calls.get('current', 0):,} / {api_calls.get('limit', 'unlimited')}")

            rollbacks = usage.get("rollbacks", {})
            click.echo(f"Rollbacks: {rollbacks.get('current', 0):,} / {rollbacks.get('limit', 'unlimited')}")

            memories = usage.get("memories", {})
            click.echo(f"Memories: {memories.get('current', 0):,} / {memories.get('limit', 'unlimited')}")

            pressure_level = usage.get("usage_pressure_level", "low")
            if pressure_level != "low":
                click.echo(f"\nUsage Pressure: {pressure_level.upper()}")
                quota_pct = usage.get("quota_percent", {})
                if quota_pct:
                    parts = []
                    for resource, pct in quota_pct.items():
                        label = resource.replace("_", " ").title()
                        parts.append(f"{label}: {pct:.0f}%")
                    click.echo(f"  Quota: {' | '.join(parts)}")

            spend = usage.get("spend_estimate")
            if spend:
                click.echo(f"\nEstimated Spend (this month):")
                click.echo(f"  Current: ${spend.get('spend_estimate_usd', 0):.4f}")
                click.echo(f"  Projected: ${spend.get('projected_spend_estimate_usd', 0):.4f}")

            projected_date = usage.get("projected_limit_date")
            if projected_date:
                click.echo(f"  Projected to hit limit by {projected_date}")

            if usage.get("budget_alert"):
                click.echo(f"\n** Budget Alert: approaching budget limit **")

            upgrade_msg = usage.get("upgrade_message")
            if upgrade_msg:
                click.echo(f"\n{upgrade_msg}")

            click.echo(f"\nAudit retention: {usage.get('audit_retention_days', 0)} days")

    except NovyxAuthError:
        print_error("Invalid API key")
        sys.exit(1)
    except NovyxError as e:
        print_error(str(e))
        sys.exit(1)


# =============================================================================
# Config Commands
# =============================================================================

@cli.group()
def config():
    """Configuration management."""
    pass


@config.command("set")
@click.argument("key")
@click.argument("value")
def config_set(key, value):
    """Set configuration value (e.g., api_key)."""
    cfg = load_config()
    cfg[key] = value
    save_config(cfg)

    print_success(f"Configuration saved: {key}")

    if key == "api_key":
        print_info(f"API key stored in {CONFIG_FILE}")


@config.command("show")
def config_show():
    """Show current configuration (API key masked)."""
    cfg = load_config()

    if not cfg:
        print_warning("No configuration found")
        print_info(f"Set API key: novyx config set api_key <key>")
        return

    if RICH_AVAILABLE:
        console = get_console()
        table = Table(title="Configuration", box=box.ROUNDED)
        table.add_column("Key", style="cyan")
        table.add_column("Value", style="yellow")

        for key, value in cfg.items():
            # Mask API key
            if key == "api_key" and value:
                masked = value[:10] + "..." + value[-4:] if len(value) > 14 else "***"
                table.add_row(key, masked)
            else:
                table.add_row(key, str(value))

        console.print(table)
    else:
        click.echo("\nConfiguration:")
        for key, value in cfg.items():
            # Mask API key
            if key == "api_key" and value:
                masked = value[:10] + "..." + value[-4:] if len(value) > 14 else "***"
                click.echo(f"  {key}: {masked}")
            else:
                click.echo(f"  {key}: {value}")

    print_info(f"Config file: {CONFIG_FILE}")


@config.command("reset")
@click.option("--yes", is_flag=True, help="Skip confirmation")
def config_reset(yes):
    """Reset configuration (delete config file)."""
    if not CONFIG_FILE.exists():
        print_info("No configuration file found")
        return

    if not yes:
        if not confirm_action("Delete configuration file?", default=False):
            print_info("Cancelled")
            return

    CONFIG_FILE.unlink()
    print_success(f"Configuration reset: {CONFIG_FILE}")


# =============================================================================
# Entry Point
# =============================================================================

def main():
    """Entry point for CLI."""
    cli(obj={})


if __name__ == "__main__":
    main()
