"""Automation profile management commands for CleverAgents CLI.

Provides add, remove, list, and show commands for automation profiles
that control plan execution autonomy, including guard enforcement hooks.

Based on v3_spec.md implementation plan Stage A6.cli.
"""

from __future__ import annotations

import contextlib
import re
import warnings
from pathlib import Path
from typing import Annotated, Any

import typer
import yaml
from pydantic import ValidationError as PydanticValidationError
from rich.panel import Panel
from rich.table import Table

from cleveragents.application.services.automation_profile_service import (
    AutomationProfileService,
)
from cleveragents.cli.formatting import OutputFormat, format_output
from cleveragents.cli.renderers import _get_console
from cleveragents.core.exceptions import (
    CleverAgentsError,
    NotFoundError,
    ValidationError,
)
from cleveragents.domain.models.core.automation_profile import (
    BUILTIN_PROFILES,
    AutomationGuard,
    AutomationProfile,
)

# Create sub-app for automation-profile commands
app = typer.Typer(
    help="Manage automation profiles that control plan execution autonomy."
)
console = _get_console()

# Reusable --format option description
_FORMAT_HELP = "Output format: json, yaml, plain, table, or rich (default: rich)"


def _get_service() -> AutomationProfileService:
    """Resolve ``AutomationProfileService`` from the DI container."""
    from cleveragents.application.container import get_container

    container = get_container()
    service: AutomationProfileService = container.automation_profile_service()
    return service


def _guards_dict(guards: AutomationGuard | None) -> dict[str, object] | None:
    """Return guard data as a dict suitable for CLI output."""
    if guards is None:
        return None
    return {
        "max_tool_calls_per_step": guards.max_tool_calls_per_step,
        "max_total_cost": guards.max_total_cost,
        "tool_allowlist": guards.tool_allowlist,
        "tool_denylist": guards.tool_denylist,
        "require_approval_for_writes": guards.require_approval_for_writes,
        "require_approval_for_apply": guards.require_approval_for_apply,
    }


def _profile_spec_dict(profile: AutomationProfile) -> dict[str, object]:
    """Return profile data as a dict suitable for CLI output.

    Structure matches the specification's JSON output format for
    ``automation-profile show`` with thresholds grouped into four
    semantic categories: phase_transitions, decision_automation,
    self_repair, and execution_controls.
    """
    source = "built-in" if profile.name in BUILTIN_PROFILES else "custom"
    result: dict[str, object] = {
        "name": profile.name,
        "description": profile.description,
        "source": source,
        "schema_version": profile.schema_version,
        "phase_transitions": {
            "decompose_task": profile.decompose_task,
            "create_tool": profile.create_tool,
            "select_tool": profile.select_tool,
        },
        "decision_automation": {
            "edit_code": profile.edit_code,
            "execute_command": profile.execute_command,
        },
        "self_repair": {
            "create_file": profile.create_file,
            "delete_content": profile.delete_content,
            "access_network": profile.access_network,
            "modify_config": profile.modify_config,
            "approve_plan": profile.approve_plan,
        },
        "execution_controls": {
            "install_dependency": profile.install_dependency,
            "require_sandbox": profile.safety.require_sandbox,
            "require_checkpoints": profile.safety.require_checkpoints,
            "allow_unsafe_tools": profile.safety.allow_unsafe_tools,
        },
        "guards": _guards_dict(profile.guards),
    }
    return result


def _threshold_summary(profile: AutomationProfile) -> str:
    """Return a compact summary of key thresholds."""
    return (
        f"decompose_task={profile.decompose_task:.1f} "
        f"create_tool={profile.create_tool:.1f} "
        f"select_tool={profile.select_tool:.1f}"
    )


def _print_profile(
    profile: AutomationProfile,
    title: str = "Automation Profile",
    fmt: str = OutputFormat.RICH.value,
) -> None:
    """Print profile details in the requested format."""
    if fmt != OutputFormat.RICH.value:
        data = _profile_spec_dict(profile)
        console.print(format_output(data, fmt))
        return

    source = "built-in" if profile.name in BUILTIN_PROFILES else "custom"

    details = (
        f"[bold]Name:[/bold] {profile.name}\n"
        f"[bold]Description:[/bold] {profile.description}\n"
        f"[bold]Source:[/bold] {source}\n"
        f"[bold]Schema Version:[/bold] {profile.schema_version}\n"
        f"\n[bold]Phase Transitions (thresholds):[/bold]\n"
        f"  decompose_task: {profile.decompose_task}\n"
        f"  create_tool: {profile.create_tool}\n"
        f"  select_tool: {profile.select_tool}\n"
        f"\n[bold]Decision Automation (thresholds):[/bold]\n"
        f"  edit_code: {profile.edit_code}\n"
        f"  execute_command: {profile.execute_command}\n"
        f"\n[bold]Self-Repair (thresholds):[/bold]\n"
        f"  create_file: {profile.create_file}\n"
        f"  delete_content: {profile.delete_content}\n"
        f"  access_network: {profile.access_network}\n"
        f"  modify_config: {profile.modify_config}\n"
        f"  approve_plan: {profile.approve_plan}\n"
        f"\n[bold]Execution Controls (thresholds):[/bold]\n"
        f"  install_dependency: {profile.install_dependency}\n"
        f"  require_sandbox: {profile.safety.require_sandbox}\n"
        f"  require_checkpoints: {profile.safety.require_checkpoints}\n"
        f"  allow_unsafe_tools: {profile.safety.allow_unsafe_tools}"
    )

    if profile.guards is not None:
        g = profile.guards
        details += (
            f"\n\n[bold]Guards:[/bold]\n"
            f"  max_tool_calls_per_step: {g.max_tool_calls_per_step}\n"
            f"  max_total_cost: {g.max_total_cost}\n"
            f"  tool_allowlist: {g.tool_allowlist}\n"
            f"  tool_denylist: {g.tool_denylist}\n"
            f"  require_approval_for_writes: "
            f"{g.require_approval_for_writes}\n"
            f"  require_approval_for_apply: "
            f"{g.require_approval_for_apply}"
        )

    console.print(Panel(details, title=title, expand=False))


@app.command("add")
def add_profile(
    config: Annotated[
        Path,
        typer.Option(
            "--config", "-c", help="Path to profile YAML config", exists=False
        ),
    ],
    update: Annotated[
        bool,
        typer.Option("--update", help="Update existing profile"),
    ] = False,
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Add or register an automation profile from a YAML config file."""
    try:
        if not config.exists():
            console.print(f"[red]Config file error:[/red] File not found: {config}")
            raise typer.Abort()

        with open(config) as fh:
            config_data: dict[str, Any] = yaml.safe_load(fh)

        if not isinstance(config_data, dict):
            console.print(
                "[red]Config file error:[/red] YAML file must contain a mapping (dict)"
            )
            raise typer.Abort()

        # Validate schema_version is present and supported
        schema_version = config_data.get("schema_version", "1.0")
        if schema_version not in ("1.0",):
            console.print(
                f"[red]Schema validation error:[/red] "
                f"Unsupported schema_version '{schema_version}'. "
                f"Supported versions: 1.0"
            )
            raise typer.Abort()

        service = _get_service()

        name = config_data.get("name", "")
        existing = None
        with contextlib.suppress(NotFoundError, CleverAgentsError):
            existing = service.get_profile(name)

        if existing and not update:
            if name in BUILTIN_PROFILES:
                console.print(
                    f"[red]Conflict:[/red] Cannot overwrite built-in profile '{name}'"
                )
            else:
                console.print(
                    f"[red]Conflict:[/red] "
                    f"Profile '{name}' already exists. "
                    f"Use --update to overwrite."
                )
            raise typer.Abort()

        if update and existing:
            profile = service.update_profile(name, config_data)
            title = "Profile Updated"
        else:
            profile = service.create_profile(config_data)
            title = "Profile Added"

        _print_profile(profile, title=title, fmt=fmt)

    except FileNotFoundError as exc:
        console.print(f"[red]Config file error:[/red] {exc}")
        raise typer.Abort() from exc
    except PydanticValidationError as exc:
        console.print(f"[red]Schema validation error:[/red] {exc}")
        raise typer.Abort() from exc
    except yaml.YAMLError as exc:
        console.print(f"[red]YAML parse error:[/red] {exc}")
        raise typer.Abort() from exc
    except ValidationError as exc:
        console.print(f"[red]Validation Error:[/red] {exc.message}")
        raise typer.Abort() from exc
    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc


@app.command("remove")
def remove_profile(
    name: Annotated[
        str,
        typer.Argument(help="Profile name to remove"),
    ],
    yes: Annotated[
        bool,
        typer.Option("--yes", "-y", help="Skip confirmation"),
    ] = False,
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Remove a custom automation profile by name."""
    try:
        service = _get_service()

        # Verify profile exists
        profile = service.get_profile(name)

        if name in BUILTIN_PROFILES:
            console.print(f"[red]Cannot remove built-in profile:[/red] '{name}'")
            raise typer.Abort()

        if not yes:
            confirm = typer.confirm(f"Remove automation profile '{name}'?")
            if not confirm:
                console.print("[yellow]Cancelled.[/yellow]")
                raise typer.Exit(0)

        service.delete_profile(name)

        if fmt != OutputFormat.RICH.value:
            data = _profile_spec_dict(profile)
            data["removed"] = True
            console.print(format_output(data, fmt))
            return

        console.print(f"[green]✓[/green] Automation profile removed: {name}")

    except NotFoundError as exc:
        console.print(f"[red]Profile not found:[/red] '{name}'")
        raise typer.Abort() from exc
    except ValidationError as exc:
        console.print(f"[red]Validation Error:[/red] {exc.message}")
        raise typer.Abort() from exc
    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc


@app.command("list")
def list_profiles(
    regex: Annotated[
        str | None,
        typer.Argument(
            help="Optional regex pattern to filter profile names",
        ),
    ] = None,
    namespace: Annotated[
        str | None,
        typer.Option(
            "--namespace",
            "-n",
            help="Filter by namespace (e.g. 'acme')",
        ),
    ] = None,
    fmt: Annotated[
        str,
        typer.Option(
            "--format",
            "-f",
            help=_FORMAT_HELP,
        ),
    ] = "rich",
) -> None:
    """List all automation profiles with optional filtering.

    Shows both built-in and custom profiles. Use ``--namespace`` to
    filter by the namespace portion of namespaced names.  A positional
    regex argument can be used to filter profile names.

    Examples:
        agents automation-profile list
        agents automation-profile list --namespace acme
        agents automation-profile list "caut.*"
        agents automation-profile list --format json
    """
    try:
        service = _get_service()

        profiles = service.list_profiles()

        # Deterministic ordering: sort by name
        profiles = sorted(profiles, key=lambda p: p.name)

        # Apply namespace filter
        if namespace:
            profiles = [
                p
                for p in profiles
                if "/" in p.name and p.name.split("/", 1)[0] == namespace
            ]

        # Apply regex filter
        if regex:
            try:
                pattern = re.compile(regex)
            except re.error as exc:
                console.print(f"[red]Invalid regex pattern:[/red] {regex}")
                raise typer.Abort() from exc
            profiles = [p for p in profiles if pattern.search(p.name)]

        if not profiles:
            console.print("[yellow]No profiles found.[/yellow]")
            return

        # Compute summary counts
        built_in_count = sum(1 for p in profiles if p.name in BUILTIN_PROFILES)
        custom_count = len(profiles) - built_in_count
        total_count = len(profiles)

        # Non-rich formats: spec-required structured envelope
        if fmt != OutputFormat.RICH.value:
            profile_entries = [
                {
                    "name": p.name,
                    "source": "built-in" if p.name in BUILTIN_PROFILES else "custom",
                    "select_tool": p.select_tool,
                    "sandbox": p.safety.require_sandbox,
                    "description": p.description,
                }
                for p in profiles
            ]
            data = {
                "profiles": profile_entries,
                "summary": {
                    "built_in": built_in_count,
                    "custom": custom_count,
                    "total": total_count,
                },
            }
            console.print(format_output(data, fmt))
            return

        # Rich table display
        table = Table(title="Automation Profiles")
        table.add_column("Name", style="cyan")
        table.add_column("Source", style="blue")
        table.add_column("Description", style="dim")
        table.add_column("Decompose", justify="right")
        table.add_column("Create Tool", justify="right")
        table.add_column("Auto-Apply", justify="right")
        table.add_column("Sandbox", justify="center")

        for profile in profiles:
            source = "built-in" if profile.name in BUILTIN_PROFILES else "custom"
            table.add_row(
                profile.name,
                source,
                profile.description[:40]
                + ("..." if len(profile.description) > 40 else ""),
                f"{profile.decompose_task:.1f}",
                f"{profile.create_tool:.1f}",
                f"{profile.select_tool:.1f}",
                "yes" if profile.safety.require_sandbox else "no",
            )

        console.print(table)

        # Summary panel
        summary_text = (
            f"[bold blue]Built-in:[/bold blue] {built_in_count}\n"
            f"[bold blue]Custom:[/bold blue] {custom_count}\n"
            f"[bold blue]Total:[/bold blue] {total_count}"
        )
        console.print(Panel(summary_text, title="Summary", expand=False))

    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc


@app.command("show")
def show_profile(
    name: Annotated[
        str,
        typer.Argument(help="Name of the automation profile to show"),
    ],
    fmt: Annotated[
        str,
        typer.Option(
            "--format",
            "-f",
            help=_FORMAT_HELP,
        ),
    ] = "rich",
) -> None:
    """Show details for an automation profile.

    Displays all thresholds, safety requirements, and metadata for the
    specified profile.

    Examples:
        agents automation-profile show manual
        agents automation-profile show acme/strict --format yaml
    """
    try:
        service = _get_service()

        profile = service.get_profile(name)

        _print_profile(profile, title="Automation Profile Details", fmt=fmt)

    except NotFoundError as exc:
        console.print(f"[red]Profile not found:[/red] '{name}'")
        raise typer.Abort() from exc
    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc


def emit_automation_level_deprecation_warning() -> None:
    """Emit a deprecation warning for the --automation-level flag.

    Called from ``plan use`` and ``plan set-automation-level`` when the
    legacy ``--automation-level`` option is used.
    """
    warnings.warn(
        "'--automation-level' is deprecated and will be removed in a "
        "future release. Use '--automation-profile' instead. "
        "Legacy levels are mapped to built-in profiles: "
        "manual->manual, supervised->supervised, auto->auto, "
        "full_auto->full-auto.",
        DeprecationWarning,
        stacklevel=3,
    )
