"""
Adapter Layer - Unified interface for all agent types.

This module defines the core abstractions that allow the orchestrator
to work with both CLI-based agents (Claude Code, Gemini CLI) and API-based
agents (Claude Agent SDK, OpenAI Agents SDK) through a single interface.

Key Classes:
- RiskLevel: Enum for risk classification
- AgentResponse: Response from agent execution
- UsageStats: Usage statistics for an agent
- BaseAdapter: Abstract base for all adapters
- LLMAdapter: Base for API-based agents
- CLIAgentAdapter: Base for CLI-based agents
- PromptBuilder: Shared prompt construction logic
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
import json
from typing import Any, AsyncIterator, Optional

from ..journal.status_packet import StatusPacket, TaskArtifacts


class RiskLevel(Enum):
    """
    Risk classification for agent actions.

    Used by the autonomy gate to determine whether actions can proceed
    automatically or require human approval.
    """

    LOW = "low"  # Auto-allowed: read files, run tests, format code
    MEDIUM = "medium"  # Edits OK, commands need approval
    HIGH = "high"  # Suggest-only, always ask user
    CRITICAL = "critical"  # Auto-reject, NEVER allow


class AgentStatus(Enum):
    """Status of an agent."""

    IDLE = "idle"
    RUNNING = "running"
    STUCK = "stuck"
    PAUSED = "paused"
    TERMINATED = "terminated"


@dataclass
class AgentResponse:
    """
    Response from agent execution.

    Contains the result of a task execution along with usage metrics
    and artifacts produced during the run.
    """

    # The main response content
    content: str

    # Token usage
    tokens_used: int = 0
    tokens_input: int = 0
    tokens_output: int = 0

    # Cost in USD
    cost: float = 0.0

    # Model used
    model: str = ""

    # Additional metadata
    metadata: dict[str, Any] = field(default_factory=dict)

    # Task artifacts (files modified, tests run, etc.)
    artifacts: TaskArtifacts = field(default_factory=TaskArtifacts)

    # Whether the execution was successful
    success: bool = True

    # Error message if not successful
    error: Optional[str] = None

    # Timestamp of completion
    completed_at: datetime = field(default_factory=datetime.now)

    def total_tokens(self) -> int:
        """Get total tokens used."""
        if self.tokens_used > 0:
            return self.tokens_used
        return self.tokens_input + self.tokens_output


@dataclass
class UsageStats:
    """
    Usage statistics for an agent.

    Tracks cumulative usage over time for budget enforcement
    and reporting.
    """

    # Token counts
    tokens_input: int = 0
    tokens_output: int = 0

    # Total cost in USD
    total_cost: float = 0.0

    # Request counts
    requests_count: int = 0
    errors_count: int = 0

    # Timing
    last_activity: datetime = field(default_factory=datetime.now)

    def total_tokens(self) -> int:
        """Get total tokens used."""
        return self.tokens_input + self.tokens_output

    def add_response(self, response: AgentResponse) -> None:
        """Update stats from an agent response."""
        self.tokens_input += response.tokens_input
        self.tokens_output += response.tokens_output
        self.total_cost += response.cost
        self.requests_count += 1
        if not response.success:
            self.errors_count += 1
        self.last_activity = datetime.now()

    def error_rate(self) -> float:
        """Calculate error rate as percentage."""
        if self.requests_count == 0:
            return 0.0
        return (self.errors_count / self.requests_count) * 100


class BaseAdapter(ABC):
    """
    Base interface for all agent adapters.

    This abstract class defines the contract that all adapters must implement,
    whether they wrap CLI tools or API clients.
    """

    def __init__(self, agent_id: str) -> None:
        """Initialize the adapter with an agent ID."""
        self.agent_id = agent_id
        self._usage_stats = UsageStats()
        self._status = AgentStatus.IDLE
        self._current_task_id: Optional[str] = None
        self._authenticated = True
        self._last_auth_error: Optional[str] = None

    @property
    def status(self) -> AgentStatus:
        """Get current agent status."""
        return self._status

    @abstractmethod
    async def execute(self, task: str, context: dict[str, Any]) -> AgentResponse:
        """
        Execute a task and return response.

        Args:
            task: The task description/prompt
            context: Additional context (project state, constraints, etc.)

        Returns:
            AgentResponse with result and usage metrics
        """
        pass

    @abstractmethod
    async def stream(self, task: str, context: dict[str, Any]) -> AsyncIterator[str]:
        """
        Stream response for real-time output.

        Args:
            task: The task description/prompt
            context: Additional context

        Yields:
            Chunks of the response as they become available
        """
        pass

    @abstractmethod
    def get_usage(self) -> UsageStats:
        """Return cumulative usage statistics."""
        pass

    @abstractmethod
    def is_healthy(self) -> bool:
        """Check if agent is responsive and can accept tasks."""
        pass

    @abstractmethod
    def write_status_packet(self) -> StatusPacket:
        """
        Write status packet at end of run.

        This is REQUIRED - every run must produce a StatusPacket
        for the orchestrator to track progress and state.
        """
        pass

    def set_task(self, task_id: str) -> None:
        """Set the current task ID."""
        self._current_task_id = task_id
        self._status = AgentStatus.RUNNING

    def clear_task(self) -> None:
        """Clear the current task."""
        self._current_task_id = None
        self._status = AgentStatus.IDLE

    async def cancel(self) -> bool:
        """
        Cancel the current execution if possible.

        Returns:
            True if cancellation was successful
        """
        self._status = AgentStatus.IDLE
        return True

    def is_authenticated(self) -> bool:
        """Return whether the agent is authenticated."""
        return self._authenticated

    def auth_error(self) -> Optional[str]:
        """Return the last authentication error, if any."""
        return self._last_auth_error

    async def check_authentication(self) -> bool:
        """Check authentication status (no-op for adapters without auth)."""
        return self._authenticated

    def get_name(self) -> str:
        """Get a human-readable name for this adapter."""
        return self.__class__.__name__


class LLMAdapter(BaseAdapter):
    """
    Base adapter for API-based models.

    Use this as the base class for adapters that communicate with
    LLM APIs (Claude Agent SDK, OpenAI Agents SDK, etc.).

    Characteristics:
    - Fast, cheap, scriptable
    - Programmatic control
    - Easy cost tracking
    - Best for: automation, scheduled tasks, pipelines
    """

    def __init__(self, agent_id: str, model: str) -> None:
        """Initialize with agent ID and model name."""
        super().__init__(agent_id)
        self.model = model

    def get_usage(self) -> UsageStats:
        """Return cumulative usage statistics."""
        return self._usage_stats

    def is_healthy(self) -> bool:
        """Check if the API is accessible."""
        # Default implementation - subclasses can override
        return self._status != AgentStatus.TERMINATED

    def write_status_packet(self) -> StatusPacket:
        """Write status packet with current state."""
        return StatusPacket(
            agent_id=self.agent_id,
            task_id=self._current_task_id or "",
            status="completed" if self._status == AgentStatus.IDLE else "running",
            artifacts=TaskArtifacts(
                tokens_input=self._usage_stats.tokens_input,
                tokens_output=self._usage_stats.tokens_output,
                cost_usd=self._usage_stats.total_cost,
            ),
        )


class CLIAgentAdapter(BaseAdapter):
    """
    Base adapter for CLI workspace agents.

    Use this as the base class for adapters that wrap CLI tools
    (Claude Code, Gemini CLI, Codex CLI).

    Characteristics:
    - Workspaces and terminal sessions
    - Git branches and file system access
    - Human-in-the-loop interaction
    - Best for: real repo edits, tests, git commits

    IMPORTANT: CLI agents typically don't persist state between invocations.
    State must be externalized via project_state.json and StatusPacket.
    """

    def __init__(self, agent_id: str, workspace_path: Optional[str] = None) -> None:
        """Initialize with agent ID and optional workspace path."""
        super().__init__(agent_id)
        self.workspace_path = workspace_path
        self._last_response: Optional[AgentResponse] = None

    def get_usage(self) -> UsageStats:
        """Return cumulative usage statistics."""
        return self._usage_stats

    def is_healthy(self) -> bool:
        """Check if the CLI tool is available and responsive."""
        # Default implementation - subclasses should check actual CLI availability
        return self._status not in (AgentStatus.STUCK, AgentStatus.TERMINATED)

    def write_status_packet(self) -> StatusPacket:
        """Write status packet with current state."""
        artifacts = TaskArtifacts(
            tokens_input=self._usage_stats.tokens_input,
            tokens_output=self._usage_stats.tokens_output,
            cost_usd=self._usage_stats.total_cost,
        )

        if self._last_response:
            artifacts = self._last_response.artifacts

        return StatusPacket(
            agent_id=self.agent_id,
            task_id=self._current_task_id or "",
            status="completed" if self._status == AgentStatus.IDLE else "running",
            artifacts=artifacts,
        )

    def inject_context(self, task: str, context: dict[str, Any]) -> str:
        """
        Inject project context into the task prompt.

        CLI agents don't persist state, so we must inject context
        at the start of each invocation.

        Args:
            task: The original task prompt
            context: Project state and constraints

        Returns:
            Enhanced prompt with context injected
        """
        context_str = ""

        if "project_state" in context:
            import json

            context_str += "## Current Project State\n"
            context_str += f"```json\n{json.dumps(context['project_state'], indent=2)}\n```\n\n"

        if "constraints" in context:
            context_str += "## Constraints\n"
            for constraint in context["constraints"]:
                context_str += f"- {constraint}\n"
            context_str += "\n"

        if "recent_decisions" in context:
            context_str += "## Recent Decisions (respect these)\n"
            for decision in context["recent_decisions"]:
                context_str += f"- {decision}\n"
            context_str += "\n"

        return f"{context_str}## Your Task\n{task}\n\n## Requirements\n- Read and respect decisions in project state\n- Output a status update at the end of your work"


class PromptBuilder:
    """
    Shared prompt construction logic for all adapters.

    Centralizes the common pattern of building prompts with:
    - Project state injection
    - Constraints
    - Recent decisions
    - Active objectives
    - Task description
    - Output requirements

    Usage:
        builder = PromptBuilder()
        prompt = builder.build(task, context)

        # Or with customization:
        prompt = builder.build(
            task,
            context,
            orchestration_context=True,
            output_requirements=["List files modified", "Run tests"],
        )
    """

    def __init__(
        self,
        include_orchestration_context: bool = False,
        custom_preamble: Optional[str] = None,
    ):
        """
        Initialize the prompt builder.

        Args:
            include_orchestration_context: Add orchestration system context
            custom_preamble: Custom text to add at the start
        """
        self.include_orchestration_context = include_orchestration_context
        self.custom_preamble = custom_preamble

    def build(
        self,
        task: str,
        context: dict[str, Any],
        output_requirements: Optional[list[str]] = None,
        additional_sections: Optional[dict[str, str]] = None,
    ) -> str:
        """
        Build a complete prompt with context injection.

        Args:
            task: The main task description
            context: Context dict with project_state, constraints, etc.
            output_requirements: List of output requirements (optional)
            additional_sections: Extra sections to add as {title: content}

        Returns:
            Complete prompt string
        """
        parts: list[str] = []

        # Custom preamble
        if self.custom_preamble:
            parts.append(self.custom_preamble)
            parts.append("")

        # Orchestration context
        if self.include_orchestration_context:
            parts.append("## Orchestration Context")
            parts.append("You are running as part of an automated orchestration system.")
            parts.append("Your work will be tracked and may be continued by other agents.")
            parts.append("")

        # Project state
        if "project_state" in context:
            parts.append("## Current Project State")
            parts.append("This is the current state - respect decisions already made:")
            parts.append(f"```json\n{json.dumps(context['project_state'], indent=2)}\n```")
            parts.append("")

        # Constraints
        if context.get("constraints"):
            parts.append("## Constraints (MUST follow)")
            for constraint in context["constraints"]:
                parts.append(f"- {constraint}")
            parts.append("")

        # Recent decisions
        if context.get("recent_decisions"):
            parts.append("## Recent Decisions (respect these)")
            for decision in context["recent_decisions"]:
                parts.append(f"- {decision}")
            parts.append("")

        # Active objectives
        if context.get("active_objectives"):
            parts.append("## Active Objectives")
            for obj in context["active_objectives"]:
                status = obj.get("status", "pending")
                parts.append(f"- [{status}] {obj.get('description', '')}")
            parts.append("")

        # Additional custom sections
        if additional_sections:
            for title, content in additional_sections.items():
                parts.append(f"## {title}")
                parts.append(content)
                parts.append("")

        # Task
        parts.append("## Your Task")
        parts.append(task)
        parts.append("")

        # Output requirements
        if output_requirements:
            parts.append("## Output Requirements")
            for req in output_requirements:
                parts.append(f"- {req}")

        return "\n".join(parts)

    @classmethod
    def for_cli_agent(cls) -> "PromptBuilder":
        """Create a builder configured for CLI agents."""
        return cls(include_orchestration_context=True)

    @classmethod
    def for_api_agent(cls) -> "PromptBuilder":
        """Create a builder configured for API agents."""
        return cls(include_orchestration_context=False)

    @classmethod
    def default_output_requirements(cls) -> list[str]:
        """Get default output requirements for agents."""
        return [
            "What was accomplished",
            "Files modified",
            "Any tests run and their results",
            "Recommended next steps",
            "Any blockers or issues encountered",
        ]

    @classmethod
    def minimal_output_requirements(cls) -> list[str]:
        """Get minimal output requirements."""
        return [
            "Follow all constraints listed above",
            "Respect decisions already made",
            "Provide a clear summary of what you accomplished",
        ]
