"""ACMS context management commands for CleverAgents CLI.

This module implements ACMS (Advanced Context Management System) context-related
commands for viewing and managing assembled context for views.

Canonical path: ``agents acms context <subcommand>``
"""

from __future__ import annotations

import fnmatch
import logging
from typing import TYPE_CHECKING, Annotated, Protocol, runtime_checkable

import typer
from rich.table import Table

from cleveragents.application.container import get_container
from cleveragents.cli.renderers import _get_console
from cleveragents.core.exceptions import CleverAgentsError
from cleveragents.domain.models.acms.tiers import ContextTier, TieredFragment

if TYPE_CHECKING:
    pass

_logger = logging.getLogger(__name__)

_BROAD_PATTERN_THRESHOLD = 50


@runtime_checkable
class _TierServiceProtocol(Protocol):
    """Protocol for ContextTierService used by _remove_fragments."""

    def _remove_from_all(self, fragment_id: str) -> None:
        """Remove a fragment from all tiers by its ID."""
        ...

    @property
    def _lock(self) -> object:
        """Thread lock for safe concurrent access."""
        ...


# AcmsContextApp is nested under "context" in main.py to form the canonical
# path ``agents acms context <command>`` (show / clear).  The app itself only
# knows about show and clear; main.py registers it as a child of an outer Typer.
app = typer.Typer(help="ACMS context management commands")


def _format_budget_utilization(
    used: int | float,
    total: int | float,
) -> str:
    """Format budget utilization as a percentage string."""
    if total == 0:
        return "0%"
    if used < 0 or total < 0:
        return "N/A"
    percentage = (used / total) * 100
    return f"{percentage:.1f}%"


def _format_fragment_row(
    fragment: TieredFragment,
) -> tuple[str, str, str, str]:
    """Extract display-ready string values from a TieredFragment.

    Returns:
        A tuple of (resource_id, tier, token_count, project_name) for display.
    """
    resource_id = fragment.resource_id or "(no resource)"
    tier_label = str(fragment.tier.value) if fragment.tier else "unknown"
    token_str = f"{fragment.token_count:,}"
    project = fragment.project_name or "(global)"
    return resource_id, tier_label, token_str, project


@app.command("show")
def acms_context_show(
    view: Annotated[
        str,
        typer.Argument(help="View name/project to show assembled context for"),
    ],
) -> None:
    """Display the assembled context for a specific ACMS view.

    Shows the context fragments stored in the hot/warm/cold tiers for
    the given project view, including token budget utilization summary.
    """
    console = _get_console()

    if not view or not view.strip():
        console.print("[red]Error:[/red] View name must not be empty.")
        raise typer.Exit(code=1)

    try:
        container = get_container()
        tier_service = container.context_tier_service()

        # Get fragments scoped to the view (project name)
        fragments = tier_service.get_scoped_view([view.strip()])

        if not fragments:
            console.print(f"[yellow]No context found for view: {view}[/yellow]")
            return

        # Display assembled context
        console.print(f"\n[bold]Assembled Context for View: {view}[/bold]")

        table = Table(title=f"Context Entries ({len(fragments)} total)")
        table.add_column("Resource", style="cyan")
        table.add_column("Tier", style="green")
        table.add_column("Tokens", style="magenta")
        table.add_column("Project", style="yellow")

        total_tokens = 0
        hot_tokens = 0
        warm_tokens = 0
        cold_tokens = 0
        for fragment in fragments:
            resource_id, tier_label, token_str, project = _format_fragment_row(fragment)
            total_tokens += fragment.token_count
            if fragment.tier == ContextTier.HOT:
                hot_tokens += fragment.token_count
            elif fragment.tier == ContextTier.WARM:
                warm_tokens += fragment.token_count
            elif fragment.tier == ContextTier.COLD:
                cold_tokens += fragment.token_count
            table.add_row(resource_id, tier_label, token_str, project)

        console.print(table)
        console.print(f"\n[bold]Total Tokens:[/bold] {total_tokens:,}")

        # Display tier metrics using actual token counts per tier
        metrics = tier_service.get_metrics()
        budget = tier_service.budget
        hot_utilization = _format_budget_utilization(
            hot_tokens, budget.max_tokens_hot or 1
        )
        warm_utilization = _format_budget_utilization(
            metrics.warm_count, budget.max_decisions_warm or 1
        )
        cold_utilization = _format_budget_utilization(
            metrics.cold_count, budget.max_decisions_cold or 1
        )
        console.print("\n[bold]Budget Utilization:[/bold]")
        console.print(
            f"  Hot tier: {metrics.hot_count} fragments"
            f" ({hot_tokens:,} tokens)"
            f" — {hot_utilization} of token budget"
        )
        console.print(
            f"  Warm tier: {metrics.warm_count} decisions"
            f" ({warm_tokens:,} tokens)"
            f" — {warm_utilization} of decision budget"
        )
        console.print(
            f"  Cold tier: {metrics.cold_count} decisions"
            f" ({cold_tokens:,} tokens)"
            f" — {cold_utilization} of decision budget"
        )

    except CleverAgentsError as e:
        _logger.exception("ACMS context show failed for view %r", view)
        console = _get_console()
        console.print(
            "[red]Error:[/red] Failed to retrieve context."
            " Please check logs for details."
        )
        raise typer.Exit(code=1) from e
    except Exception as e:
        _logger.exception("Unexpected error in acms_context_show for view %r", view)
        console = _get_console()
        console.print(
            f"[red]Error:[/red] Failed to retrieve context for view '{view}'."
        )
        raise typer.Exit(code=1) from e


@app.command("clear")
def acms_context_clear(
    path: Annotated[
        str | None,
        typer.Option("--path", help="Filter by resource path pattern (glob)"),
    ] = None,
    tag: Annotated[
        str | None,
        typer.Option("--tag", help="Filter by metadata tag value"),
    ] = None,
    tier: Annotated[
        str | None,
        typer.Option(
            "--tier",
            help="Filter by tier (hot, warm, cold)",
        ),
    ] = None,
    yes: Annotated[
        bool, typer.Option("--yes", "-y", help="Skip confirmation prompt")
    ] = False,
) -> None:
    """Remove stale context entries from the ACMS index.

    Removes context fragments by path pattern, metadata tag, or tier.
    Requires confirmation unless --yes flag is provided.
    """
    console = _get_console()

    try:
        container = get_container()
        tier_service = container.context_tier_service()

        # Get all fragments and apply filters
        all_fragments = tier_service.get_all_fragments()

        # Warn if no filters specified (clear ALL)
        if not path and not tag and not tier:
            console.print(
                "[yellow]Warning:[/yellow] No filters specified. "
                "This will remove ALL context entries."
            )

        # Apply filters
        entries_to_remove = _filter_fragments(all_fragments, path, tag, tier)

        if not entries_to_remove:
            console.print(
                "[yellow]No context entries match the specified filters.[/yellow]"
            )
            return

        # Warn on overly broad glob patterns that match many entries
        if path and len(entries_to_remove) > _BROAD_PATTERN_THRESHOLD:
            console.print(
                f"[yellow]Warning:[/yellow] The path pattern '{path}' matches "
                f"{len(entries_to_remove)} entries."
                " Consider using a more specific pattern."
            )

        # Show what will be removed
        console.print(
            f"\n[bold]Context entries to remove "
            f"({len(entries_to_remove)} total):[/bold]"
        )

        table = Table()
        table.add_column("Resource", style="cyan")
        table.add_column("Tier", style="green")
        table.add_column("Project", style="yellow")

        for fragment in entries_to_remove[:10]:  # Show first 10
            resource_id, tier_label, _, project = _format_fragment_row(fragment)
            table.add_row(resource_id, tier_label, project)

        console.print(table)

        if len(entries_to_remove) > 10:
            console.print(f"  ... and {len(entries_to_remove) - 10} more entries")

        # Confirm if needed — handle cancellation before the main try/except
        # to avoid catching typer.Exit from the confirmation prompt
        if not yes:
            confirmed = typer.confirm(
                f"\nRemove {len(entries_to_remove)} context entries?"
            )
            if not confirmed:
                console.print("[yellow]Cancelled.[/yellow]")
                return

        # Remove entries by evicting from their respective tiers
        removed_count = _remove_fragments(tier_service, entries_to_remove)
        console.print(f"\n[green]✓[/green] Removed {removed_count} context entries.")

    except CleverAgentsError as e:
        _logger.exception("ACMS context clear failed")
        console = _get_console()
        console.print(
            "[red]Error:[/red] Failed to clear context entries."
            " Please check logs for details."
        )
        raise typer.Exit(code=1) from e
    except Exception as e:
        _logger.exception("Unexpected error in acms_context_clear")
        console = _get_console()
        console.print("[red]Error:[/red] Failed to clear context entries.")
        raise typer.Exit(code=1) from e


def _filter_fragments(
    fragments: list[TieredFragment],
    path: str | None,
    tag: str | None,
    tier_filter: str | None,
) -> list[TieredFragment]:
    """Filter fragments by path pattern, tag, and/or tier.

    Args:
        fragments: All fragments to filter.
        path: Glob pattern to match against resource_id.
        tag: Tag value to match in fragment metadata.
        tier_filter: Tier name to filter by (hot, warm, cold).

    Returns:
        Filtered list of fragments matching all specified criteria.
    """
    result = fragments

    if path:
        result = [f for f in result if fnmatch.fnmatch(f.resource_id, path)]

    if tag:
        result = [
            f
            for f in result
            if f.metadata.get("tag") == tag or f.metadata.get("tags") == tag
        ]

    if tier_filter:
        try:
            target_tier = ContextTier(tier_filter.lower())
            result = [f for f in result if f.tier == target_tier]
        except ValueError:
            # Invalid tier name — return empty (no matches)
            return []

    return result


def _remove_fragments(
    tier_service: _TierServiceProtocol,
    fragments: list[TieredFragment],
) -> int:
    """Remove the given fragments from the tier service.

    Removes fragments by calling _remove_from_all on the tier service.
    ContextTierService is thread-safe (RLock-protected); manual locking
    here would duplicate internal synchronization and risk mismatches.

    Args:
        tier_service: The ContextTierService instance (or compatible implementation).
        fragments: Fragments to remove.

    Returns:
        Number of fragments successfully removed.
    """
    removed = 0
    for fragment in fragments:
        tier_service._remove_from_all(fragment.fragment_id)
        removed += 1

    return removed


@app.callback(invoke_without_command=True)
def acms_context_default(ctx: typer.Context) -> None:
    """Show ACMS context information when no subcommand is provided."""
    if ctx.invoked_subcommand is None:
        console = _get_console()
        console.print("[bold]ACMS Context Management[/bold]")
        console.print("\nAvailable commands:")
        console.print("  show <view>  - Display assembled context for a view")
        console.print("  clear        - Remove context entries with filtering")
        console.print(
            "\nUse 'agents acms context <command> --help' for more information."
        )
