"""Context compression suggestions for Oclawma.

This module provides proactive suggestions when approaching context limits,
with one-command compression and detailed token usage breakdowns.
"""

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Callable, Optional

from oclawma.context.budget import BudgetThreshold, ContextBudget


class CompressionAction(Enum):
    """Available compression actions."""

    DROP_FILES = "drop_files"
    SUMMARIZE = "summarize"
    ARCHIVE = "archive"
    CLEAR_ATTACHMENTS = "clear_attachments"
    RESET = "reset"
    COMPACT = "compact"


@dataclass
class TokenBreakdown:
    """Token usage breakdown by category."""

    system_prompt: int = 0
    conversation_history: int = 0
    file_contents: int = 0
    tool_results: int = 0
    attachments: int = 0
    other: int = 0

    @property
    def total(self) -> int:
        """Get total token count."""
        return (
            self.system_prompt
            + self.conversation_history
            + self.file_contents
            + self.tool_results
            + self.attachments
            + self.other
        )

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "system_prompt": self.system_prompt,
            "conversation_history": self.conversation_history,
            "file_contents": self.file_contents,
            "tool_results": self.tool_results,
            "attachments": self.attachments,
            "other": self.other,
            "total": self.total,
        }

    def visualize(self, budget: int = 8192) -> str:
        """Create a visual representation of token usage.

        Args:
            budget: Total token budget

        Returns:
            Visual breakdown string
        """
        lines = []
        total = self.total

        categories = [
            ("System Prompt", self.system_prompt, "🔧"),
            ("Conversation", self.conversation_history, "💬"),
            ("Files", self.file_contents, "📄"),
            ("Tool Results", self.tool_results, "🛠️"),
            ("Attachments", self.attachments, "📎"),
            ("Other", self.other, "⚪"),
        ]

        max_name_len = max(len(name) for name, _, _ in categories)

        for name, tokens, icon in categories:
            if tokens == 0:
                continue

            pct = (tokens / budget) * 100 if budget > 0 else 0
            bar_width = 20
            filled = int((tokens / budget) * bar_width) if budget > 0 else 0
            bar = "█" * filled + "░" * (bar_width - filled)

            lines.append(
                f"{icon} {name.ljust(max_name_len)}: {bar} " f"{tokens:,} tokens ({pct:.1f}%)"
            )

        lines.append("-" * 50)
        total_pct = (total / budget) * 100 if budget > 0 else 0
        lines.append(
            f"📊 {'Total'.ljust(max_name_len)}: {total:,} tokens ({total_pct:.1f}% of budget)"
        )

        return "\n".join(lines)


@dataclass
class CompressionSuggestion:
    """A single compression suggestion."""

    action: CompressionAction
    title: str
    description: str
    estimated_savings: int
    priority: int = 0  # Higher = more important
    command: Optional[str] = None
    auto_executable: bool = False

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "action": self.action.value,
            "title": self.title,
            "description": self.description,
            "estimated_savings": self.estimated_savings,
            "priority": self.priority,
            "command": self.command,
            "auto_executable": self.auto_executable,
        }


@dataclass
class CompressionPlan:
    """A complete compression plan with multiple suggestions."""

    current_usage: int
    budget: int
    usage_percent: float
    threshold: BudgetThreshold
    suggestions: list[CompressionSuggestion] = field(default_factory=list)
    token_breakdown: Optional[TokenBreakdown] = None
    message: Optional[str] = None

    @property
    def total_potential_savings(self) -> int:
        """Get total potential savings from all suggestions."""
        return sum(s.estimated_savings for s in self.suggestions)

    @property
    def projected_usage(self) -> int:
        """Get projected usage after applying all suggestions."""
        return max(0, self.current_usage - self.total_potential_savings)

    @property
    def projected_percent(self) -> float:
        """Get projected usage percentage after compression."""
        if self.budget == 0:
            return 0.0
        return (self.projected_usage / self.budget) * 100

    def get_executable_suggestions(self) -> list[CompressionSuggestion]:
        """Get suggestions that can be auto-executed."""
        return [s for s in self.suggestions if s.auto_executable]

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "current_usage": self.current_usage,
            "budget": self.budget,
            "usage_percent": round(self.usage_percent, 2),
            "threshold": self.threshold.value,
            "suggestions": [s.to_dict() for s in self.suggestions],
            "token_breakdown": self.token_breakdown.to_dict() if self.token_breakdown else None,
            "total_potential_savings": self.total_potential_savings,
            "projected_usage": self.projected_usage,
            "projected_percent": round(self.projected_percent, 2),
            "message": self.message,
        }


class ContextCompressionSuggestions:
    """Proactive context compression suggestions.

    This class detects when context is filling up and suggests
    actions to reduce token usage, including:
    - Dropping files from context
    - Summarizing conversation history
    - Archiving old context
    - Clearing attachments

    Attributes:
        budget: ContextBudget instance to monitor
        on_suggestion: Optional callback when suggestions are generated
    """

    def __init__(
        self,
        budget: Optional[ContextBudget] = None,
        on_suggestion: Optional[Callable[[CompressionPlan], None]] = None,
    ):
        """Initialize the context compression suggestions.

        Args:
            budget: ContextBudget instance to monitor
            on_suggestion: Callback when suggestions are generated
        """
        self.budget = budget or ContextBudget()
        self.on_suggestion = on_suggestion
        self._history: list[dict[str, Any]] = []

    def analyze(
        self,
        history: Optional[Any] = None,
        breakdown: Optional[TokenBreakdown] = None,
    ) -> CompressionPlan:
        """Analyze current context and generate compression plan.

        Args:
            history: Optional conversation history to analyze
            breakdown: Optional pre-calculated token breakdown

        Returns:
            CompressionPlan with suggestions
        """
        status = self.budget.get_status()

        # Generate token breakdown if not provided
        if breakdown is None and history is not None:
            breakdown = self._calculate_breakdown(history)

        suggestions: list[CompressionSuggestion] = []

        # Only suggest actions if we're approaching limits
        if status.threshold in (
            BudgetThreshold.WARNING,
            BudgetThreshold.CRITICAL,
            BudgetThreshold.EXHAUSTED,
        ):
            suggestions.extend(self._generate_suggestions(history, breakdown))

        # Sort by priority (highest first) then by savings (highest first)
        suggestions.sort(key=lambda s: (-s.priority, -s.estimated_savings))

        # Generate message based on threshold
        message = self._generate_message(status, suggestions)

        plan = CompressionPlan(
            current_usage=self.budget.used_tokens,
            budget=self.budget.total_budget,
            usage_percent=self.budget.usage_percent,
            threshold=status.threshold,
            suggestions=suggestions,
            token_breakdown=breakdown,
            message=message,
        )

        # Record in history
        self._history.append(
            {
                "timestamp": datetime.now().isoformat(),
                "usage": self.budget.used_tokens,
                "suggestions_count": len(suggestions),
            }
        )

        if self.on_suggestion:
            self.on_suggestion(plan)

        return plan

    def should_suggest_compression(self, threshold: Optional[BudgetThreshold] = None) -> bool:
        """Check if compression suggestions should be shown.

        Args:
            threshold: Minimum threshold to trigger suggestions

        Returns:
            True if suggestions should be shown
        """
        status = self.budget.get_status()
        min_threshold = threshold or BudgetThreshold.WARNING

        # Map thresholds to numeric values for comparison
        threshold_levels = {
            BudgetThreshold.NORMAL: 0,
            BudgetThreshold.WARNING: 1,
            BudgetThreshold.CRITICAL: 2,
            BudgetThreshold.EXHAUSTED: 3,
        }

        return threshold_levels[status.threshold] >= threshold_levels[min_threshold]

    def _generate_suggestions(
        self,
        history: Optional[Any],
        breakdown: Optional[TokenBreakdown],
    ) -> list[CompressionSuggestion]:
        """Generate compression suggestions based on context.

        Args:
            history: Conversation history
            breakdown: Token breakdown

        Returns:
            List of compression suggestions
        """
        suggestions = []

        # Suggestion: Summarize conversation (always available if history exists)
        if history is not None and len(history) > 3:
            estimated_savings = self._estimate_summarization_savings(history)
            if estimated_savings > 500:
                suggestions.append(
                    CompressionSuggestion(
                        action=CompressionAction.SUMMARIZE,
                        title="Summarize Conversation History",
                        description="Summarize older conversation turns while keeping recent ones verbatim. Key facts will be preserved.",
                        estimated_savings=estimated_savings,
                        priority=3,
                        command="/compact",
                        auto_executable=True,
                    )
                )

        # Suggestion: Compact (one-command compression)
        if self.budget.usage_percent >= 60:
            suggestions.append(
                CompressionSuggestion(
                    action=CompressionAction.COMPACT,
                    title="One-Command Compact",
                    description="Run full compression: summarize history, clear attachments, and optimize context.",
                    estimated_savings=int(self.budget.used_tokens * 0.4),
                    priority=5,
                    command="oclawma compact",
                    auto_executable=True,
                )
            )

        # Suggestion: Clear attachments (if we detect attachments)
        if breakdown and breakdown.attachments > 0:
            suggestions.append(
                CompressionSuggestion(
                    action=CompressionAction.CLEAR_ATTACHMENTS,
                    title="Clear Attachments",
                    description=f"Remove attached files from context ({breakdown.attachments:,} tokens).",
                    estimated_savings=breakdown.attachments,
                    priority=4,
                    command="/clear attachments",
                    auto_executable=True,
                )
            )

        # Suggestion: Drop files (if we detect large file contents)
        if breakdown and breakdown.file_contents > 1000:
            suggestions.append(
                CompressionSuggestion(
                    action=CompressionAction.DROP_FILES,
                    title="Drop Large Files",
                    description=f"Remove large file contents from context ({breakdown.file_contents:,} tokens).",
                    estimated_savings=breakdown.file_contents,
                    priority=2,
                    command="/clear files",
                    auto_executable=False,  # Requires user confirmation
                )
            )

        # Suggestion: Archive (for very high usage)
        if self.budget.usage_percent >= 85:
            suggestions.append(
                CompressionSuggestion(
                    action=CompressionAction.ARCHIVE,
                    title="Archive & Start Fresh",
                    description="Archive current conversation to file and start a new clean session.",
                    estimated_savings=self.budget.used_tokens,
                    priority=1,
                    command="/archive",
                    auto_executable=False,  # User should confirm
                )
            )

        # Suggestion: Reset (last resort)
        if self.budget.usage_percent >= 95:
            suggestions.append(
                CompressionSuggestion(
                    action=CompressionAction.RESET,
                    title="Reset Session",
                    description="Clear all context and start fresh. This will lose conversation history.",
                    estimated_savings=self.budget.used_tokens,
                    priority=0,
                    command="/clear",
                    auto_executable=False,  # User should confirm
                )
            )

        return suggestions

    def _calculate_breakdown(self, history: Any) -> TokenBreakdown:
        """Calculate token breakdown from conversation history.

        Args:
            history: Conversation history

        Returns:
            TokenBreakdown with categorized token counts
        """
        breakdown = TokenBreakdown()

        if not hasattr(history, "get_messages"):
            return breakdown

        messages = history.get_messages()

        for msg in messages:
            content = msg.content if hasattr(msg, "content") else str(msg)
            metadata = msg.metadata if hasattr(msg, "metadata") else {}
            role = msg.role if hasattr(msg, "role") else "unknown"

            # Estimate tokens (rough approximation)
            tokens = len(content) // 4

            # Categorize based on role and metadata
            if role == "system":
                breakdown.system_prompt += tokens
            elif metadata.get("type") == "tool_result":
                breakdown.tool_results += tokens
            elif metadata.get("type") == "file_content":
                breakdown.file_contents += tokens
            elif metadata.get("type") == "attachment":
                breakdown.attachments += tokens
            elif role in ("user", "assistant"):
                breakdown.conversation_history += tokens
            else:
                breakdown.other += tokens

        return breakdown

    def _estimate_summarization_savings(self, history: Any) -> int:
        """Estimate tokens saved by summarizing history.

        Args:
            history: Conversation history

        Returns:
            Estimated token savings
        """
        if not hasattr(history, "get_messages"):
            return 0

        messages = history.get_messages()
        if len(messages) <= 3:
            return 0

        # Rough estimate: we keep last 3 exchanges verbatim, summarize rest
        # Assume summary is ~30% of original size
        total_tokens = sum(len(m.content) // 4 for m in messages)
        kept_tokens = 500  # Rough estimate for 3 exchanges

        if total_tokens > kept_tokens:
            return int((total_tokens - kept_tokens) * 0.7)

        return 0

    def _generate_message(
        self,
        status: Any,
        suggestions: list[CompressionSuggestion],
    ) -> Optional[str]:
        """Generate a user-friendly message about the compression plan.

        Args:
            status: Budget status
            suggestions: List of suggestions

        Returns:
            Message string or None
        """
        if status.threshold == BudgetThreshold.NORMAL:
            return None

        if status.threshold == BudgetThreshold.WARNING:
            if suggestions:
                return (
                    f"⚠️  Context budget at {self.budget.usage_percent:.0f}%. "
                    f"Run 'oclawma compact' to free up ~{sum(s.estimated_savings for s in suggestions[:2]):,} tokens."
                )
            return f"⚠️  Context budget at {self.budget.usage_percent:.0f}%."

        if status.threshold == BudgetThreshold.CRITICAL:
            if suggestions:
                best = suggestions[0]
                return (
                    f"🚨 Context budget at {self.budget.usage_percent:.0f}%! "
                    f"Run 'oclawma compact' now to free ~{best.estimated_savings:,} tokens."
                )
            return f"🚨 Context budget at {self.budget.usage_percent:.0f}%!"

        if status.threshold == BudgetThreshold.EXHAUSTED:
            return (
                f"🛑 Context budget exhausted ({self.budget.usage_percent:.0f}%)! "
                "You must compress context or start a new session."
            )

        return None

    def format_suggestions(self, plan: CompressionPlan, verbose: bool = False) -> str:
        """Format compression plan as user-friendly text.

        Args:
            plan: Compression plan to format
            verbose: Whether to include verbose output

        Returns:
            Formatted string
        """
        lines = []

        # Header
        if plan.threshold == BudgetThreshold.WARNING:
            lines.append("⚠️  CONTEXT COMPRESSION SUGGESTIONS")
        elif plan.threshold == BudgetThreshold.CRITICAL:
            lines.append("🚨 CONTEXT COMPRESSION RECOMMENDED")
        elif plan.threshold == BudgetThreshold.EXHAUSTED:
            lines.append("🛑 CONTEXT CRITICAL - ACTION REQUIRED")
        else:
            lines.append("📊 CONTEXT COMPRESSION OPTIONS")

        lines.append("═" * 50)

        # Current status
        lines.append(
            f"Current usage: {plan.current_usage:,} / {plan.budget:,} tokens ({plan.usage_percent:.1f}%)"
        )
        lines.append("")

        # Token breakdown
        if plan.token_breakdown and plan.token_breakdown.total > 0:
            lines.append("📈 TOKEN USAGE BREAKDOWN")
            lines.append("─" * 50)
            lines.append(plan.token_breakdown.visualize(plan.budget))
            lines.append("")

        # Suggestions
        if plan.suggestions:
            lines.append("💡 SUGGESTED ACTIONS")
            lines.append("─" * 50)

            for i, suggestion in enumerate(plan.suggestions[:5], 1):
                icon = "✓" if suggestion.auto_executable else "○"
                lines.append(f"{icon} {i}. {suggestion.title}")
                lines.append(f"   {suggestion.description}")
                lines.append(f"   Estimated savings: ~{suggestion.estimated_savings:,} tokens")
                if suggestion.command:
                    lines.append(f"   Command: {suggestion.command}")
                lines.append("")

            # Summary
            lines.append("─" * 50)
            lines.append(f"Total potential savings: ~{plan.total_potential_savings:,} tokens")
            lines.append(
                f"Projected usage after: {plan.projected_usage:,} tokens ({plan.projected_percent:.1f}%)"
            )
            lines.append("")

            # Quick action
            auto_suggestions = plan.get_executable_suggestions()
            if auto_suggestions:
                lines.append("🚀 QUICK ACTION")
                lines.append("─" * 50)
                lines.append("Run: oclawma compact")
                lines.append(
                    f"   This will execute the top {len(auto_suggestions)} auto-executable suggestions."
                )
        else:
            lines.append("✓ No compression suggestions at this time.")

        return "\n".join(lines)

    def get_history(self) -> list[dict[str, Any]]:
        """Get history of compression suggestions.

        Returns:
            List of historical suggestion events
        """
        return self._history.copy()


def create_compression_suggestions(
    budget: Optional[ContextBudget] = None,
    history: Optional[Any] = None,
) -> CompressionPlan:
    """Convenience function to create compression suggestions.

    Args:
        budget: Context budget
        history: Conversation history

    Returns:
        Compression plan with suggestions
    """
    suggester = ContextCompressionSuggestions(budget=budget)
    return suggester.analyze(history=history)
