"""Persona management commands for CadeCoder."""

from pathlib import Path
from typing import Annotated

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

from cadecoder.core.config import get_config
from cadecoder.core.errors import StorageError
from cadecoder.storage.personas import get_persona_store

persona_app = typer.Typer(
    name="persona",
    help="Manage AI personas (custom system prompts)",
    invoke_without_command=True,
)

console = Console(stderr=True)


def _sanitize_persona_name(name: str) -> str:
    """Sanitize persona name for safe use in filesystem paths."""
    import re

    # Strip path traversal and dangerous characters
    sanitized = name.replace("/", "_").replace("\\", "_").replace("..", "_").replace("\x00", "")
    # Only allow alphanumeric, hyphens, underscores
    sanitized = re.sub(r"[^a-zA-Z0-9_-]", "_", sanitized)
    if not sanitized:
        raise typer.BadParameter("Persona name must contain at least one alphanumeric character.")
    return sanitized


def _ensure_memory_dir(persona_name: str) -> Path:
    """Create the per-persona memory directory for agent-library.

    Returns the created directory path.
    """
    safe_name = _sanitize_persona_name(persona_name)
    memory_dir = Path(get_config().app_dir) / "memory" / safe_name
    memory_dir.mkdir(parents=True, exist_ok=True)
    (memory_dir / "documents").mkdir(exist_ok=True)
    return memory_dir


@persona_app.callback()
def persona_callback(ctx: typer.Context) -> None:
    """Manage AI personas"""
    if ctx.invoked_subcommand is None:
        console.print(ctx.get_help())
        raise typer.Exit(0)


@persona_app.command("list")
def list_personas() -> None:
    """List all saved personas.

    Shows personas sorted by name with their descriptions and mode.
    """
    store = get_persona_store()
    personas = store.list_personas()

    if not personas:
        console.print("[yellow]No personas found.[/yellow]")
        console.print("[dim]Create one with: cade persona create <name> --prompt '...'[/dim]")
        return

    # Check for default persona
    try:
        config = get_config()
        default_persona = config.settings.default_persona
    except Exception:
        default_persona = None

    table = Table(title="AI Personas", show_header=True, header_style="bold cyan")
    table.add_column("Name", style="bold", max_width=20)
    table.add_column("Description", max_width=40)
    table.add_column("Mode", style="magenta", max_width=10)
    table.add_column("ID", style="dim", max_width=14)

    for persona in personas:
        mode = "replace" if persona.replace_base else "prepend"
        name_display = persona.name
        if default_persona and persona.name == default_persona:
            name_display = f"{persona.name} [green](default)[/green]"

        table.add_row(
            name_display,
            persona.description or "-",
            mode,
            persona.persona_id[:12] + "...",
        )

    console.print(table)
    console.print(f"\n[dim]Showing {len(personas)} persona(s)[/dim]")
    console.print("[dim]Use 'cade --persona <name>' to use a persona[/dim]")


@persona_app.command("create")
def create_persona(
    name: Annotated[
        str, typer.Argument(help="Unique name for the persona (e.g., 'code-reviewer')")
    ],
    prompt: Annotated[
        str | None,
        typer.Option("--prompt", "-p", help="System prompt text"),
    ] = None,
    prompt_file: Annotated[
        Path | None,
        typer.Option("--prompt-file", "-f", help="Path to file containing system prompt"),
    ] = None,
    description: Annotated[
        str | None,
        typer.Option("--description", "-d", help="Description of what this persona does"),
    ] = None,
    replace_base: Annotated[
        bool,
        typer.Option(
            "--replace/--prepend",
            "-r/-P",
            help="Replace base prompt entirely (--replace) or prepend to it (--prepend, default)",
        ),
    ] = False,
) -> None:
    """Create a new persona with a custom system prompt.

    The persona can either replace the base system prompt entirely (--replace)
    or be prepended to it (--prepend, default).

    Examples
    --------
    - cade persona create reviewer --prompt "You are a code reviewer..."
    - cade persona create anna --prompt-file ~/.cade/personas/anna.md --replace
    - cade persona create helper -p "Be helpful and concise" -d "A concise helper"
    """
    # Validate input
    if not prompt and not prompt_file:
        console.print("[red]Error: Must provide either --prompt or --prompt-file[/red]")
        raise typer.Exit(1)

    if prompt and prompt_file:
        console.print("[red]Error: Cannot use both --prompt and --prompt-file[/red]")
        raise typer.Exit(1)

    # Load prompt from file if specified
    system_prompt = prompt
    if prompt_file:
        try:
            system_prompt = prompt_file.read_text(encoding="utf-8")
        except FileNotFoundError:
            console.print(f"[red]Error: File not found: {prompt_file}[/red]")
            raise typer.Exit(1)
        except Exception as e:
            console.print(f"[red]Error reading file: {e}[/red]")
            raise typer.Exit(1)

    if not system_prompt or not system_prompt.strip():
        console.print("[red]Error: System prompt cannot be empty[/red]")
        raise typer.Exit(1)

    # Create the persona
    store = get_persona_store()
    try:
        persona = store.create_persona(
            name=name,
            system_prompt=system_prompt.strip(),
            description=description,
            replace_base=replace_base,
        )

        # Create per-persona memory directory for agent-library
        _ensure_memory_dir(name)

        mode = "replace" if replace_base else "prepend"
        console.print(f"[green]Created persona '{persona.name}' (mode: {mode})[/green]")
        console.print(f"[dim]ID: {persona.persona_id}[/dim]")
        console.print(f"[dim]Use with: cade --persona {persona.name}[/dim]")
    except StorageError as e:
        console.print(f"[red]Error: {e}[/red]")
        raise typer.Exit(1)


@persona_app.command("get")
def get_persona(
    identifier: Annotated[str, typer.Argument(help="Persona name, ID, or partial ID")],
    show_prompt: Annotated[
        bool,
        typer.Option("--prompt", "-p", help="Show the full system prompt"),
    ] = False,
) -> None:
    """Get details about a specific persona.

    You can provide a persona name, ID, or partial ID.
    """
    store = get_persona_store()
    persona = store.find_persona(identifier)

    if not persona:
        console.print(f"[red]Persona '{identifier}' not found.[/red]")
        console.print("[dim]Use 'cade persona list' to see available personas[/dim]")
        raise typer.Exit(1)

    # Display persona info
    console.print("\n[bold cyan]Persona Details[/bold cyan]")
    console.print(f"  [bold]ID:[/bold] {persona.persona_id}")
    console.print(f"  [bold]Name:[/bold] {persona.name}")
    console.print(f"  [bold]Description:[/bold] {persona.description or '-'}")
    mode = (
        "replace (fully replaces base prompt)"
        if persona.replace_base
        else "prepend (added before base prompt)"
    )
    console.print(f"  [bold]Mode:[/bold] {mode}")
    console.print(f"  [bold]Created:[/bold] {persona.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
    console.print(
        f"  [bold]Last Modified:[/bold] {persona.last_modified_at.strftime('%Y-%m-%d %H:%M:%S')}"
    )

    # Show prompt if requested
    if show_prompt:
        console.print("\n[bold cyan]System Prompt[/bold cyan]")
        console.print("-" * 40)
        console.print(persona.system_prompt)
        console.print("-" * 40)
    else:
        # Show preview
        preview = persona.system_prompt[:200]
        if len(persona.system_prompt) > 200:
            preview += "..."
        console.print(f"\n[bold]Prompt Preview:[/bold] {preview}")
        console.print("[dim]Use --prompt to see the full system prompt[/dim]")

    console.print(f"\n[dim]Use with: cade --persona {persona.name}[/dim]")


@persona_app.command("edit")
def edit_persona(
    identifier: Annotated[str, typer.Argument(help="Persona name, ID, or partial ID")],
    name: Annotated[
        str | None,
        typer.Option("--name", "-n", help="New name for the persona"),
    ] = None,
    prompt: Annotated[
        str | None,
        typer.Option("--prompt", "-p", help="New system prompt text"),
    ] = None,
    prompt_file: Annotated[
        Path | None,
        typer.Option("--prompt-file", "-f", help="Path to file containing new system prompt"),
    ] = None,
    description: Annotated[
        str | None,
        typer.Option("--description", "-d", help="New description"),
    ] = None,
    replace_base: Annotated[
        bool | None,
        typer.Option(
            "--replace/--prepend",
            "-r/-P",
            help="Change mode: replace base prompt or prepend to it",
        ),
    ] = None,
) -> None:
    """Edit an existing persona.

    Only the specified options will be updated.

    Examples
    --------
    - cade persona edit reviewer --description "Updated description"
    - cade persona edit reviewer --prompt "New prompt text"
    - cade persona edit reviewer --replace  # Change to replace mode
    """
    store = get_persona_store()
    persona = store.find_persona(identifier)

    if not persona:
        console.print(f"[red]Persona '{identifier}' not found.[/red]")
        raise typer.Exit(1)

    # Handle prompt file
    system_prompt = prompt
    if prompt_file:
        if prompt:
            console.print("[red]Error: Cannot use both --prompt and --prompt-file[/red]")
            raise typer.Exit(1)
        try:
            system_prompt = prompt_file.read_text(encoding="utf-8").strip()
        except Exception as e:
            console.print(f"[red]Error reading file: {e}[/red]")
            raise typer.Exit(1)

    # Check if anything to update
    if all(v is None for v in [name, system_prompt, description, replace_base]):
        console.print("[yellow]No changes specified.[/yellow]")
        raise typer.Exit(0)

    try:
        updated = store.update_persona(
            persona_id=persona.persona_id,
            name=name,
            system_prompt=system_prompt,
            description=description,
            replace_base=replace_base,
        )
        if updated:
            console.print(f"[green]Updated persona '{updated.name}'[/green]")
        else:
            console.print("[red]Failed to update persona[/red]")
            raise typer.Exit(1)
    except StorageError as e:
        console.print(f"[red]Error: {e}[/red]")
        raise typer.Exit(1)


@persona_app.command("delete")
def delete_persona(
    identifier: Annotated[str, typer.Argument(help="Persona name, ID, or partial ID to delete")],
    force: Annotated[
        bool,
        typer.Option("--force", "-f", help="Skip confirmation"),
    ] = False,
) -> None:
    """Delete a persona."""
    store = get_persona_store()
    persona = store.find_persona(identifier)

    if not persona:
        console.print(f"[red]Persona '{identifier}' not found.[/red]")
        raise typer.Exit(1)

    if not force:
        confirm = typer.confirm(f"Delete persona '{persona.name}'?")
        if not confirm:
            console.print("[yellow]Cancelled.[/yellow]")
            raise typer.Abort()

    if store.delete_persona(persona.persona_id):
        console.print(f"[green]Deleted persona '{persona.name}'[/green]")
    else:
        console.print("[red]Failed to delete persona[/red]")
        raise typer.Exit(1)


@persona_app.command("default")
def set_default_persona(
    name: Annotated[
        str | None,
        typer.Argument(help="Persona name to set as default (omit to show current)"),
    ] = None,
    clear: Annotated[
        bool,
        typer.Option("--clear", "-c", help="Clear the default persona"),
    ] = False,
) -> None:
    """Set or show the default persona.

    The default persona is automatically used when no --persona flag is provided.

    Examples
    --------
    - cade persona default           # Show current default
    - cade persona default reviewer  # Set 'reviewer' as default
    - cade persona default --clear   # Clear the default
    """
    from cadecoder.core.config import _load_cadecoder_settings, _save_cadecoder_settings

    if clear:
        # Clear the default persona
        try:
            settings = _load_cadecoder_settings()
            if hasattr(settings, "default_persona"):
                settings.default_persona = None
            _save_cadecoder_settings(settings)
            console.print("[green]Cleared default persona[/green]")
        except Exception as e:
            console.print(f"[red]Error clearing default: {e}[/red]")
            raise typer.Exit(1)
        return

    if name is None:
        # Show current default
        try:
            settings = _load_cadecoder_settings()
            default = getattr(settings, "default_persona", None)
            if default:
                console.print(f"[cyan]Default persona: {default}[/cyan]")
            else:
                console.print("[dim]No default persona set[/dim]")
                console.print("[dim]Set one with: cade persona default <name>[/dim]")
        except Exception:
            console.print("[dim]No default persona set[/dim]")
        return

    # Set the default persona
    store = get_persona_store()
    persona = store.find_persona(name)

    if not persona:
        console.print(f"[red]Persona '{name}' not found.[/red]")
        console.print("[dim]Use 'cade persona list' to see available personas[/dim]")
        raise typer.Exit(1)

    try:
        settings = _load_cadecoder_settings()
        # We need to add default_persona to the settings model
        # For now, store in a simple way
        settings.default_persona = persona.name
        _save_cadecoder_settings(settings)
        console.print(f"[green]Set default persona to '{persona.name}'[/green]")
        console.print(
            "[dim]This persona will be used automatically unless --persona is specified[/dim]"
        )
    except Exception as e:
        console.print(f"[red]Error setting default: {e}[/red]")
        raise typer.Exit(1)


@persona_app.command("replace")
def replace_persona(
    name: Annotated[str, typer.Argument(help="Persona name to use as the Cade replacement")],
) -> None:
    """Replace the default Cade persona with a specified persona.

    This command sets the specified persona as the default AND ensures it uses
    'replace' mode (fully replaces the base Cade prompt rather than prepending).

    This is a convenience command that combines:
    1. Setting replace_base=True on the persona
    2. Setting it as the default persona

    Examples
    --------
    - cade persona replace anna  # Make 'anna' the default, replacing Cade
    """
    from cadecoder.core.config import _load_cadecoder_settings, _save_cadecoder_settings

    store = get_persona_store()
    persona = store.find_persona(name)

    if not persona:
        console.print(f"[red]Persona '{name}' not found.[/red]")
        console.print("[dim]Use 'cade persona list' to see available personas[/dim]")
        console.print(
            "[dim]Create one with: cade persona create <name> --prompt '...' --replace[/dim]"
        )
        raise typer.Exit(1)

    # Ensure the persona is in replace mode
    if not persona.replace_base:
        try:
            updated = store.update_persona(
                persona_id=persona.persona_id,
                replace_base=True,
            )
            if updated:
                console.print(f"[cyan]Updated '{persona.name}' to replace mode[/cyan]")
            else:
                console.print("[red]Failed to update persona mode[/red]")
                raise typer.Exit(1)
        except StorageError as e:
            console.print(f"[red]Error updating persona: {e}[/red]")
            raise typer.Exit(1)

    # Set as default persona
    try:
        settings = _load_cadecoder_settings()
        settings.default_persona = persona.name
        _save_cadecoder_settings(settings)
        console.print(f"[green]Set '{persona.name}' as the default Cade replacement[/green]")
        console.print(
            "[dim]Cade will now use this persona's system prompt instead of the default[/dim]"
        )
    except Exception as e:
        console.print(f"[red]Error setting default: {e}[/red]")
        raise typer.Exit(1)
