"""MNEMOSYNTH CLI — Premium Terminal UI with Rich.

Professional CLI with panels, tables, spinners, trees, and syntax highlighting.
No emoji — clean, production-grade output like Claude, Vercel, and Prisma CLIs.

Commands:
  serve     Start the MCP server
  stats     Memory dashboard
  search    Search with formatted results
  inspect   Browse memories as a tree
  dream     Dream consolidation
  health    System diagnostics
  export    Export memories
  forget    Delete a memory
  reset     Reset all memories
"""

from __future__ import annotations

import json
import sys
import time

import click
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.tree import Tree
from rich.text import Text
from rich.columns import Columns
from rich.syntax import Syntax
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from rich.rule import Rule
from rich.markdown import Markdown
from rich import box

console = Console()

# ── Theme colors ─────────────────────────────────────────────────

COLORS = {
    "episodic": "dodger_blue2",
    "semantic": "green3",
    "procedural": "yellow",
    "active": "green",
    "deprecated": "dim",
    "quarantined": "red",
    "accent": "magenta",
    "muted": "bright_black",
    "brand": "cyan",
}

TYPE_LABELS = {
    "episodic": "EPIS",
    "semantic": "SEMA",
    "procedural": "PROC",
}


def print_header():
    """Print the MNEMOSYNTH branded header."""
    console.print()
    console.print(
        Panel(
            "[bold cyan]MNEMOSYNTH[/bold cyan]  [dim]v0.1.0[/dim]\n"
            "[bright_black]Universal AI Memory Plugin — by Vasudev Jaiswal[/bright_black]",
            border_style="cyan",
            box=box.DOUBLE,
            padding=(0, 2),
        )
    )


# ── Main CLI Group ───────────────────────────────────────────────

@click.group()
@click.version_option(package_name="mnemosynth")
def main():
    """MNEMOSYNTH — Universal AI Memory Plugin

    Persistent, verified, hallucination-resistant memory for ANY AI system.
    Developed by Vasudev Jaiswal.
    """
    pass


# ── serve ────────────────────────────────────────────────────────

@main.command()
@click.option("--transport", type=click.Choice(["stdio", "sse"]), default="stdio",
              help="Transport type (stdio for local, sse for remote)")
def serve(transport: str):
    """Start the MCP server for Claude/Cursor/Windsurf."""
    print_header()

    tools_table = Table(box=box.ROUNDED, border_style="cyan", show_header=True, header_style="bold cyan")
    tools_table.add_column("Tool", style="green")
    tools_table.add_column("Description", style="white")
    tools_table.add_row("add_memory", "Store new memory (auto-classifies)")
    tools_table.add_row("search_memory", "Semantic search across all types")
    tools_table.add_row("get_digest", "Compressed XML context block")
    tools_table.add_row("get_contradictions", "Find conflicting memories")
    tools_table.add_row("run_dream", "Dream consolidation (cluster + promote)")
    tools_table.add_row("forget", "Delete by ID")
    tools_table.add_row("get_stats", "Memory statistics")
    tools_table.add_row("get_provenance", "Full audit trail")

    console.print(Panel(tools_table, title="[bold]MCP Tools[/bold]", border_style="dim"))
    console.print(f"\n  [green]>[/green] Transport: [bold]{transport}[/bold]")
    console.print(f"  [green]>[/green] Waiting for MCP client connection...\n")

    from mnemosynth.mcp.server import serve as mcp_serve
    try:
        mcp_serve(transport=transport)
    except KeyboardInterrupt:
        console.print("\n  [yellow]Server stopped.[/yellow]")
    except Exception as e:
        console.print(f"\n  [red]x Server error:[/red] {e}")
        sys.exit(1)


# ── stats ────────────────────────────────────────────────────────

@main.command()
def stats():
    """Show memory statistics dashboard."""
    print_header()

    from mnemosynth import Mnemosynth
    brain = Mnemosynth()
    s = brain.stats()

    counts_table = Table(box=box.SIMPLE_HEAD, show_edge=False, pad_edge=False)
    counts_table.add_column("Type", style="bold")
    counts_table.add_column("Count", justify="right", style="bold")
    counts_table.add_column("Bar", min_width=20)

    total = max(s["total"], 1)
    for mtype, label, color in [
        ("episodic", "Episodic", "dodger_blue2"),
        ("semantic", "Semantic", "green3"),
        ("procedural", "Procedural", "yellow"),
    ]:
        count = s[mtype]
        bar_width = int(20 * count / total) if total > 0 else 0
        bar = f"[{color}]{'#' * bar_width}[/{color}][dim]{'.' * (20 - bar_width)}[/dim]"
        counts_table.add_row(f"  {label}", str(count), bar)

    console.print(Panel(
        counts_table,
        title=f"[bold]Memory Dashboard[/bold]  [dim]Total: {s['total']}[/dim]",
        border_style="cyan",
        padding=(1, 2),
    ))

    status_table = Table(box=None, show_header=False, pad_edge=False)
    status_table.add_row(
        f"  [green]Active: {s['active']}[/green]",
        f"  [dim]Deprecated: {s['deprecated']}[/dim]",
        f"  [red]Quarantined: {s['quarantined']}[/red]",
    )
    console.print(status_table)
    console.print()


# ── search ───────────────────────────────────────────────────────

@main.command()
@click.argument("query")
@click.option("--limit", "-n", type=int, default=5, help="Maximum results")
def search(query: str, limit: int):
    """Search memories by query — semantic search across all types."""
    print_header()
    console.print(f"  Searching: [bold italic]{query}[/bold italic]\n")

    from mnemosynth import Mnemosynth

    with Progress(
        SpinnerColumn("dots"),
        TextColumn("[cyan]Searching memory stores..."),
        console=console,
        transient=True,
    ) as progress:
        progress.add_task("search", total=None)
        brain = Mnemosynth()
        results = brain.recall(query, limit=limit)

    if not results:
        console.print(Panel(
            "[dim]No matching memories found.[/dim]\n"
            "[bright_black]Try a different query or add some memories first.[/bright_black]",
            border_style="yellow",
            title="No Results",
        ))
        return

    table = Table(
        box=box.ROUNDED,
        border_style="cyan",
        show_header=True,
        header_style="bold cyan",
        title=f"[bold]Found {len(results)} memories[/bold]",
    )
    table.add_column("#", style="dim", width=3, justify="center")
    table.add_column("Type", width=12)
    table.add_column("Content", ratio=3)
    table.add_column("Confidence", width=12, justify="center")
    table.add_column("Age", width=10, justify="right", style="bright_black")

    for i, mem in enumerate(results, 1):
        color = COLORS.get(mem.memory_type.value, "white")
        label = TYPE_LABELS.get(mem.memory_type.value, "")

        conf_pct = int(mem.confidence * 100)
        filled = conf_pct // 10
        conf_bar = f"[{color}]{'|' * filled}[/{color}][dim]{'.' * (10 - filled)}[/dim] {conf_pct}%"

        from datetime import datetime, timezone
        age_days = (datetime.now(timezone.utc) - mem.created_at).days
        age_str = f"{age_days}d" if age_days > 0 else "today"

        table.add_row(
            str(i),
            f"[{color}]{label}[/{color}]",
            mem.content[:100] + ("..." if len(mem.content) > 100 else ""),
            conf_bar,
            age_str,
        )

    console.print(table)
    console.print()


# ── inspect ──────────────────────────────────────────────────────

@main.command()
@click.option("--type", "memory_type", type=click.Choice(["all", "episodic", "semantic", "procedural"]),
              default="all", help="Filter by memory type")
@click.option("--limit", "-n", type=int, default=30, help="Maximum items")
def inspect(memory_type: str, limit: int):
    """Browse stored memories as a visual tree."""
    print_header()

    from mnemosynth import Mnemosynth
    from mnemosynth.core.types import MemoryType

    brain = Mnemosynth()
    mt = None if memory_type == "all" else MemoryType(memory_type)
    memories = brain.db.get_memories(memory_type=mt, limit=limit)

    if not memories:
        console.print(Panel(
            "[dim]No memories stored yet.[/dim]\n"
            "[bright_black]Start chatting with an MCP client or use the Python API.[/bright_black]",
            border_style="yellow",
            title="Empty Memory",
        ))
        return

    grouped: dict[str, list] = {"episodic": [], "semantic": [], "procedural": []}
    for mem in memories:
        grouped[mem.memory_type.value].append(mem)

    tree = Tree(
        f"[bold cyan]Memory Store[/bold cyan] [dim]({len(memories)} memories)[/dim]",
        guide_style="cyan",
    )

    for mtype, color in [
        ("episodic", "dodger_blue2"),
        ("semantic", "green3"),
        ("procedural", "yellow"),
    ]:
        items = grouped[mtype]
        if not items:
            continue

        branch = tree.add(
            f"[{color} bold]{mtype.upper()}[/{color} bold] [dim]({len(items)})[/dim]"
        )

        for mem in items:
            status_mark = "+" if mem.status.value == "active" else "-" if mem.status.value == "deprecated" else "!"
            conf = int(mem.confidence * 100)
            branch.add(
                f"[{color}]{mem.content[:80]}[/{color}]\n"
                f"     [dim]{mem.id[:8]}... | {conf}% | accessed {mem.access_count}x | {mem.created_at.strftime('%Y-%m-%d %H:%M')}[/dim]"
            )

    console.print(Panel(tree, border_style="dim", padding=(1, 2)))
    console.print()


# ── dream ────────────────────────────────────────────────────────

@main.command()
@click.option("--verbose", "-v", is_flag=True, help="Show detailed output")
def dream(verbose: bool):
    """Run dream consolidation — cluster, promote, decay."""
    print_header()

    from mnemosynth import Mnemosynth

    console.print(Panel(
        "[bold magenta]Dream Mode[/bold magenta]\n\n"
        "[dim]Consolidating memories like the brain does during sleep:\n"
        "  1. Cluster recent episodic memories (HDBSCAN)\n"
        "  2. Promote dense patterns to semantic knowledge\n"
        "  3. Decay stale/outlier memories[/dim]",
        border_style="magenta",
    ))

    with Progress(
        SpinnerColumn("dots"),
        TextColumn("[magenta]{task.description}"),
        BarColumn(bar_width=30),
        console=console,
    ) as progress:
        task = progress.add_task("Entering dream state...", total=100)

        brain = Mnemosynth()
        progress.update(task, advance=20, description="Loading episodic memories...")
        time.sleep(0.3)

        progress.update(task, advance=20, description="Clustering with HDBSCAN...")
        time.sleep(0.3)

        progress.update(task, advance=20, description="Promoting patterns...")
        report = brain.dream()
        time.sleep(0.2)

        progress.update(task, advance=20, description="Applying decay...")
        time.sleep(0.2)

        progress.update(task, advance=20, description="Dream complete.")
        time.sleep(0.3)

    results = Table(box=None, show_header=False, padding=(0, 2))
    results.add_column("Metric")
    results.add_column("Value", justify="right", style="bold")
    results.add_row("Promoted to semantic", f"[green]{report['promoted']}[/green]")
    results.add_row("Contradictions resolved", f"[yellow]{report['contradictions_resolved']}[/yellow]")
    results.add_row("Stale memories decayed", f"[red]{report['decayed']}[/red]")

    console.print(Panel(results, title="[bold]Dream Report[/bold]", border_style="magenta"))
    console.print()


# ── health ───────────────────────────────────────────────────────

@main.command()
def health():
    """Run full system diagnostics."""
    print_header()

    from mnemosynth import Mnemosynth
    import os

    with Progress(
        SpinnerColumn("dots"),
        TextColumn("[cyan]Running diagnostics..."),
        console=console,
        transient=True,
    ) as progress:
        progress.add_task("health", total=None)
        brain = Mnemosynth()
        s = brain.stats()
        config = brain.config

    sys_table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2))
    sys_table.add_column("Check", style="bold")
    sys_table.add_column("Status", justify="right")

    data_exists = os.path.exists(config.data_dir)
    sys_table.add_row("Data Directory", f"[green]OK[/green] {config.data_dir}" if data_exists else f"[red]MISSING[/red]")

    db_exists = os.path.exists(config.db_path)
    if db_exists:
        db_size = os.path.getsize(config.db_path)
        db_str = f"{db_size / 1024:.1f} KB" if db_size < 1024 * 1024 else f"{db_size / (1024 * 1024):.1f} MB"
        sys_table.add_row("SQLite Database", f"[green]OK[/green] {db_str}")
    else:
        sys_table.add_row("SQLite Database", "[yellow]PENDING[/yellow] Not created yet")

    vec_exists = os.path.exists(config.vectors_dir)
    sys_table.add_row("Vector Store (LanceDB)", f"[green]OK[/green]" if vec_exists else "[yellow]PENDING[/yellow]")

    config_exists = config.config_path.exists()
    sys_table.add_row("Config File", f"[green]OK[/green] Loaded" if config_exists else "[dim]DEFAULT[/dim]")

    sys_table.add_row("Embedding Model", f"[green]OK[/green] {config.embedding_model}")
    sys_table.add_row("Total Memories", f"[cyan]{s['total']}[/cyan]")
    sys_table.add_row("Decay Engine", f"[green]ON[/green]" if config.decay.enabled else "[dim]OFF[/dim]")
    sys_table.add_row("Contradiction Detection", f"[green]ON[/green]" if config.contradiction.enabled else "[dim]OFF[/dim]")
    sys_table.add_row("Sentiment Analysis", f"[green]ON[/green]" if config.sentiment.enabled else "[dim]OFF[/dim]")
    sys_table.add_row("Dream Auto-Schedule", f"[green]ON[/green]" if config.dream.auto_schedule else "[dim]OFF[/dim]")

    console.print(Panel(sys_table, title="[bold]System Health[/bold]", border_style="cyan", padding=(1, 2)))

    config_table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2))
    config_table.add_column("Setting", style="dim")
    config_table.add_column("Value")
    config_table.add_row("Decay half-life", f"{config.decay.half_life_days} days")
    config_table.add_row("Min confidence", f"{config.decay.min_confidence}")
    config_table.add_row("Contradiction threshold", f"{config.contradiction.threshold}")
    config_table.add_row("Digest max tokens", f"{config.digest.max_tokens}")
    config_table.add_row("Sentiment boost", f"{config.sentiment.boost_factor}x")

    console.print(Panel(config_table, title="[bold]Configuration[/bold]", border_style="dim"))
    console.print()


# ── export ───────────────────────────────────────────────────────

@main.command()
@click.option("--format", "fmt", type=click.Choice(["json", "markdown"]), default="json", help="Output format")
@click.option("--output", "-o", type=click.Path(), default=None, help="Output file path")
def export(fmt: str, output: str | None):
    """Export all memories as JSON or Markdown."""

    from mnemosynth import Mnemosynth

    with Progress(
        SpinnerColumn("dots"),
        TextColumn("[cyan]Exporting memories..."),
        console=console,
        transient=True,
    ) as progress:
        progress.add_task("export", total=None)
        brain = Mnemosynth()
        memories = brain.db.get_memories(limit=100000)

    if fmt == "json":
        data = [mem.to_dict() for mem in memories]
        content = json.dumps(data, indent=2, default=str)
    else:
        lines = ["# MNEMOSYNTH Memory Export\n"]
        for mem in memories:
            lines.append(f"## [{mem.memory_type.value.upper()}] {mem.content[:80]}")
            lines.append(f"- **ID:** `{mem.id}`")
            lines.append(f"- **Confidence:** {mem.confidence:.2%}")
            lines.append(f"- **Created:** {mem.created_at.strftime('%Y-%m-%d %H:%M')}")
            lines.append(f"- **Status:** {mem.status.value}")
            lines.append("")
        content = "\n".join(lines)

    if output:
        with open(output, "w") as f:
            f.write(content)
        console.print(f"\n  [green]>[/green] Exported {len(memories)} memories to [bold]{output}[/bold]\n")
    else:
        if fmt == "json":
            console.print(Syntax(content, "json", theme="monokai", line_numbers=True))
        else:
            console.print(Markdown(content))


# ── forget ───────────────────────────────────────────────────────

@main.command("forget")
@click.argument("memory_id")
def forget_cmd(memory_id: str):
    """Delete a specific memory by ID."""
    print_header()

    from mnemosynth import Mnemosynth
    brain = Mnemosynth()

    memory = brain.db.get_memory(memory_id)
    if not memory:
        console.print(f"\n  [red]x[/red] Memory [bold]{memory_id}[/bold] not found.\n")
        return

    color = COLORS.get(memory.memory_type.value, "white")
    label = TYPE_LABELS.get(memory.memory_type.value, "")

    console.print(Panel(
        f"[{color}]{memory.content}[/{color}]\n\n"
        f"[dim]Type: {memory.memory_type.value} | "
        f"Confidence: {memory.confidence:.0%} | "
        f"Created: {memory.created_at.strftime('%Y-%m-%d %H:%M')}[/dim]",
        title="[bold red]Memory to Delete[/bold red]",
        border_style="red",
    ))

    if click.confirm("\n  Delete this memory?"):
        brain.forget(memory_id)
        console.print(f"\n  [green]>[/green] Memory deleted.\n")
    else:
        console.print(f"\n  [yellow]Cancelled.[/yellow]\n")


# ── reset ────────────────────────────────────────────────────────

@main.command()
@click.option("--confirm", is_flag=True, help="Skip confirmation prompt")
def reset(confirm: bool):
    """Reset all memories — DESTRUCTIVE."""
    print_header()

    if not confirm:
        console.print(Panel(
            "[bold red]WARNING[/bold red]\n\n"
            "[red]This will permanently delete ALL stored memories,\n"
            "including all episodic events, semantic facts, and\n"
            "procedural knowledge.[/red]\n\n"
            "[dim]This action cannot be undone.[/dim]",
            border_style="red",
            title="[bold red]Destructive Operation[/bold red]",
        ))

        if not click.confirm("\n  Are you absolutely sure?"):
            console.print(f"\n  [yellow]Cancelled.[/yellow]\n")
            return

    import shutil
    from mnemosynth.core.config import DEFAULT_DATA_DIR

    try:
        shutil.rmtree(DEFAULT_DATA_DIR, ignore_errors=True)
        console.print(f"\n  [green]>[/green] All memories reset. Starting fresh.\n")
    except Exception as e:
        console.print(f"\n  [red]x[/red] Reset failed: {e}\n")


if __name__ == "__main__":
    main()
