"""Cache management commands."""

from pathlib import Path
from typing import Optional

import click
from rich.console import Console
from rich.table import Table

from scry_run.cache import ScryCache
from scry_run.home import get_app_dir, get_apps_dir

console = Console()


@click.group()
def cache() -> None:
    """Manage the scry-run code cache."""
    pass


@cache.command("list")
@click.argument("app_name", required=False)
@click.option("--keys", "-k", is_flag=True, help="Output keys only (Class.Attribute)")
def list_entries(app_name: Optional[str], keys: bool) -> None:
    """List cached code entries.

    Without app name, lists all apps and their entry counts.
    With app name, lists entries for that specific app.
    """
    if app_name:
        # List entries for specific app
        app_dir = get_app_dir(app_name)
        cache_file = app_dir / "cache.json"

        if not cache_file.exists():
            console.print(f"[red]App '{app_name}' not found or has no cache.[/red]")
            return

        llm_cache = ScryCache(cache_file)
        entries = llm_cache.list_entries()

        if not entries:
            if not keys:
                console.print(f"[yellow]No cached entries for app '{app_name}'.[/yellow]")
            return

        if keys:
            for entry in sorted(entries, key=lambda e: (e.class_name, e.attr_name)):
                console.print(f"{entry.class_name}.{entry.attr_name}")
            return

        table = Table(title=f"Cached Code Entries ({app_name})")
        table.add_column("Class", style="cyan")
        table.add_column("Attribute", style="green")
        table.add_column("Type", style="magenta")
        table.add_column("Created", style="dim")
        table.add_column("Description")

        for entry in sorted(entries, key=lambda e: (e.class_name, e.attr_name)):
            # Truncate long descriptions
            desc = entry.docstring[:50] + "..." if len(entry.docstring) > 50 else entry.docstring

            table.add_row(
                entry.class_name,
                entry.attr_name,
                entry.code_type,
                entry.created_at[:10],  # Just the date
                desc,
            )

        console.print(table)
        console.print(f"\n[dim]Total: {len(entries)} entries[/dim]")
    else:
        # List all apps with entry counts
        apps_dir = get_apps_dir()
        if not apps_dir.exists():
            console.print("[yellow]No apps found.[/yellow]")
            return

        app_paths = [p for p in apps_dir.iterdir() if p.is_dir()]
        if not app_paths:
            console.print("[yellow]No apps found.[/yellow]")
            return

        for app_path in sorted(app_paths):
            cache_file = app_path / "cache.json"
            if cache_file.exists():
                llm_cache = ScryCache(cache_file)
                entries = llm_cache.list_entries()
                console.print(f"{app_path.name}: {len(entries)} entries")
            else:
                console.print(f"{app_path.name}: 0 entries")


@cache.command("show")
@click.argument("app_name")
@click.argument("key")
def show_entry(app_name: str, key: str) -> None:
    """Show details of a specific cached entry.

    KEY should be in format Class.method (e.g., MyApp.process_data).
    """
    app_dir = get_app_dir(app_name)
    cache_file = app_dir / "cache.json"

    if not cache_file.exists():
        console.print(f"[red]App '{app_name}' not found or has no cache.[/red]")
        raise SystemExit(1)

    # Parse key into class_name and attr_name
    if "." not in key:
        console.print(f"[red]Invalid key format:[/red] {key}")
        console.print("[dim]Expected format: Class.method (e.g., MyApp.process_data)[/dim]")
        raise SystemExit(1)

    class_name, attr_name = key.split(".", 1)

    llm_cache = ScryCache(cache_file)
    entry = llm_cache.get(class_name, attr_name)

    if not entry:
        console.print(f"[red]Entry not found:[/red] {key} in app '{app_name}'")
        raise SystemExit(1)

    console.print(f"[bold cyan]{entry.class_name}[/bold cyan].[bold green]{entry.attr_name}[/bold green]")
    console.print(f"[dim]Type:[/dim] {entry.code_type}")
    console.print(f"[dim]Created:[/dim] {entry.created_at}")
    console.print(f"[dim]Checksum:[/dim] {entry.checksum[:16]}...")
    console.print()

    if entry.docstring:
        console.print(f"[bold]Description:[/bold] {entry.docstring}")
        console.print()

    if entry.dependencies:
        console.print("[bold]Dependencies:[/bold]")
        for dep in entry.dependencies:
            console.print(f"  {dep}")
        console.print()

    console.print("[bold]Code:[/bold]")
    console.print("```python")
    console.print(entry.code)
    console.print("```")


@cache.command("export")
@click.argument("app_name")
@click.option(
    "--output", "-o",
    type=click.Path(dir_okay=False, path_type=Path),
    help="Output file path (default: stdout as JSON)",
)
@click.option(
    "--format", "-f",
    type=click.Choice(["json", "python"]),
    default="json",
    help="Output format",
)
def export_cache(
    app_name: str,
    output: Optional[Path],
    format: str,
) -> None:
    """Export cached code for an app."""
    import json

    app_dir = get_app_dir(app_name)
    cache_file = app_dir / "cache.json"

    if not cache_file.exists():
        console.print(f"[red]App '{app_name}' not found or has no cache.[/red]")
        raise SystemExit(1)

    llm_cache = ScryCache(cache_file)

    if format == "python":
        if not output:
            console.print("[red]Error:[/red] --output required for Python format")
            raise SystemExit(1)

        llm_cache.export_to_file(output)
        console.print(f"[green]Exported to:[/green] {output}")
    else:
        data = llm_cache.export()

        if output:
            with open(output, "w") as f:
                json.dump(data, f, indent=2)
            console.print(f"[green]Exported to:[/green] {output}")
        else:
            console.print_json(data=data)


@cache.command("prune")
@click.argument("app_name")
@click.option(
    "--class", "class_name",
    help="Only prune entries for this class",
)
@click.option(
    "--attr", "attr_name",
    help="Only prune this specific attribute (requires --class)",
)
@click.option(
    "--all", "prune_all",
    is_flag=True,
    help="Prune all entries without confirmation",
)
def prune_cache(
    app_name: str,
    class_name: Optional[str],
    attr_name: Optional[str],
    prune_all: bool,
) -> None:
    """Remove cached entries for an app."""
    from rich.prompt import Confirm

    app_dir = get_app_dir(app_name)
    cache_file = app_dir / "cache.json"

    if not cache_file.exists():
        console.print(f"[red]App '{app_name}' not found or has no cache.[/red]")
        raise SystemExit(1)

    llm_cache = ScryCache(cache_file)

    if attr_name and not class_name:
        console.print("[red]Error:[/red] --attr requires --class")
        raise SystemExit(1)

    # Describe what will be pruned
    if class_name and attr_name:
        target = f"{class_name}.{attr_name}"
    elif class_name:
        target = f"all entries for {class_name}"
    else:
        target = "all cached entries"

    # Confirm unless --all flag
    if not prune_all:
        entries = llm_cache.list_entries()
        if class_name:
            entries = [e for e in entries if e.class_name == class_name]
            if attr_name:
                entries = [e for e in entries if e.attr_name == attr_name]

        if not entries:
            console.print(f"[yellow]No matching entries found for app '{app_name}'.[/yellow]")
            return

        console.print(f"Will prune [bold]{len(entries)}[/bold] entries: {target}")

        if not Confirm.ask("Continue?"):
            console.print("[yellow]Aborted.[/yellow]")
            return

    # Prune
    count = llm_cache.prune(class_name=class_name, attr_name=attr_name)

    if count > 0:
        console.print(f"[green]Pruned {count} entries.[/green]")
    else:
        console.print("[yellow]No entries to prune.[/yellow]")


@cache.command("rm")
@click.argument("app_name")
@click.argument("keys", nargs=-1)
def remove_entry(app_name: str, keys: tuple[str, ...]) -> None:
    """Remove specific cache entries from an app.

    KEY format:
    - ClassName.method_name  (remove specific method)
    - ClassName              (remove all entries for class)
    """
    if not keys:
        console.print("[yellow]No keys provided. usage: scry-run cache rm <app-name> Class.method[/yellow]")
        return

    app_dir = get_app_dir(app_name)
    cache_file = app_dir / "cache.json"

    if not cache_file.exists():
        console.print(f"[red]App '{app_name}' not found or has no cache.[/red]")
        raise SystemExit(1)

    llm_cache = ScryCache(cache_file)
    total_pruned = 0

    for key in keys:
        if "." in key:
            # Remove specific attribute
            class_name, attr_name = key.split(".", 1)
            if llm_cache.delete(class_name, attr_name):
                console.print(f"[green]Removed[/green] {class_name}.{attr_name}")
                total_pruned += 1
            else:
                console.print(f"[red]Not found:[/red] {class_name}.{attr_name}")
        else:
            # Remove entire class
            class_name = key
            pruned = llm_cache.prune(class_name=class_name)
            if pruned > 0:
                console.print(f"[green]Removed[/green] {class_name} ({pruned} entries)")
                total_pruned += pruned
            else:
                console.print(f"[red]Not found:[/red] {class_name}")

    if total_pruned > 0:
        console.print(f"\n[bold green]Total removed: {total_pruned}[/bold green]")


@cache.command("reset")
@click.argument("app_name")
@click.option(
    "--force", "-f",
    is_flag=True,
    help="Force reset without confirmation",
)
def reset_cache(app_name: str, force: bool) -> None:
    """Reset the entire cache for an app (delete all entries)."""
    from rich.prompt import Confirm

    app_dir = get_app_dir(app_name)
    cache_file = app_dir / "cache.json"

    if not cache_file.exists():
        console.print(f"[red]App '{app_name}' not found or has no cache.[/red]")
        raise SystemExit(1)

    if not force:
        console.print(f"[bold red]WARNING:[/bold red] This will delete ALL cached code for app '{app_name}'.")
        if not Confirm.ask("Are you sure you want to reset the cache?"):
            console.print("[yellow]Aborted.[/yellow]")
            return

    llm_cache = ScryCache(cache_file)
    count = llm_cache.prune()

    console.print(f"[bold green]Cache reset complete. Removed {count} entries.[/bold green]")
