"""Claude SDK backend implementation.

Wraps the Claude Agent SDK to provide the Backend interface.
This is the default backend when using Claude Code.
"""

from collections.abc import AsyncIterator
from pathlib import Path
from typing import Any

from cleave.tui.backends.base import (
    Backend,
    BackendAuthError,
    BackendConfig,
    BackendConnectionError,
    BackendState,
    Message,
    MessageType,
)

# Defer SDK import - it's optional
_SDK_AVAILABLE: bool | None = None


def _check_sdk() -> bool:
    """Check if Claude SDK is available."""
    global _SDK_AVAILABLE
    if _SDK_AVAILABLE is None:
        try:
            import claude_agent_sdk  # noqa: F401

            _SDK_AVAILABLE = True
        except ImportError:
            _SDK_AVAILABLE = False
    return _SDK_AVAILABLE


class ClaudeBackend(Backend):
    """Backend using Claude Agent SDK.

    Requires:
        - claude-agent-sdk package installed
        - Authentication via Claude CLI (~/.claude/.credentials.json)

    This backend supports the full Claude Code feature set including
    tool use, multi-turn conversations, and skill invocation.
    """

    # Default models
    MODELS = [
        "claude-sonnet-4-20250514",
        "claude-opus-4-20250514",
        "claude-haiku-4-20250514",
    ]

    # Model name aliases
    MODEL_ALIASES = {
        "sonnet": "claude-sonnet-4-20250514",
        "opus": "claude-opus-4-20250514",
        "haiku": "claude-haiku-4-20250514",
    }

    def __init__(self) -> None:
        """Initialize Claude backend."""
        self._state = BackendState.DISCONNECTED
        self._error: str | None = None
        self._client: Any = None
        self._config: BackendConfig | None = None
        self._streaming_active: bool = False

    @property
    def name(self) -> str:
        """Human-readable backend name."""
        return "Claude"

    @property
    def state(self) -> BackendState:
        """Current connection state."""
        return self._state

    @property
    def error(self) -> str | None:
        """Last error message."""
        return self._error

    async def connect(self, config: BackendConfig) -> None:
        """Connect to Claude SDK.

        Args:
            config: Backend configuration.

        Raises:
            BackendConnectionError: If SDK unavailable or connection fails.
            BackendAuthError: If not authenticated.
        """
        if self._state == BackendState.CONNECTED:
            return

        if not _check_sdk():
            raise BackendConnectionError(
                "Claude SDK not installed. Install with: pip install claude-agent-sdk"
            )

        # Check authentication
        is_auth, msg = self.check_auth()
        if not is_auth:
            raise BackendAuthError(msg)

        self._state = BackendState.CONNECTING
        self._error = None
        self._config = config

        try:
            from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient

            # Resolve model alias
            model = config.model or "claude-sonnet-4-20250514"
            model = self.MODEL_ALIASES.get(model, model)

            options = ClaudeAgentOptions(
                max_turns=config.max_turns,
                include_partial_messages=True,  # Enable streaming deltas
            )

            if config.system_prompt:
                options.system_prompt = config.system_prompt

            if config.allowed_tools:
                options.allowed_tools = config.allowed_tools

            if config.disallowed_tools:
                options.disallowed_tools = config.disallowed_tools

            self._client = ClaudeSDKClient(options=options)
            await self._client.__aenter__()
            self._state = BackendState.CONNECTED
            self._streaming_active = False  # Track streaming state

        except Exception as e:
            self._state = BackendState.ERROR
            self._error = str(e)
            raise BackendConnectionError(f"Failed to connect: {e}") from e

    async def disconnect(self) -> None:
        """Disconnect from Claude SDK."""
        if self._client is not None:
            try:
                await self._client.__aexit__(None, None, None)
            except Exception:
                pass  # Best effort cleanup
            finally:
                self._client = None
                self._state = BackendState.DISCONNECTED

    async def query(self, prompt: str) -> AsyncIterator[Message]:
        """Send a prompt and stream responses.

        Args:
            prompt: User message to send.

        Yields:
            Message objects as they arrive.

        Raises:
            RuntimeError: If not connected.
        """
        if self._state != BackendState.CONNECTED or self._client is None:
            raise RuntimeError("Backend not connected. Call connect() first.")

        try:
            await self._client.query(prompt)

            async for event in self._client.receive_response():
                message = self._parse_event(event)
                if message:
                    yield message

        except Exception as e:
            self._state = BackendState.ERROR
            self._error = str(e)
            raise

    def _parse_event(self, event: Any) -> Message | None:
        """Parse SDK event into a Message.

        Args:
            event: Raw SDK event.

        Returns:
            Message if displayable content, None otherwise.
        """
        from claude_agent_sdk.types import AssistantMessage, ResultMessage, SystemMessage

        # Handle StreamEvent for token-level streaming
        if hasattr(event, "event") and hasattr(event.event, "type"):
            return self._parse_stream_event(event.event)

        if isinstance(event, AssistantMessage):
            # Complete message - end any active streaming
            text_parts = []
            for block in event.content:
                if hasattr(block, "text"):
                    text_parts.append(block.text)
            if text_parts:
                content = "".join(text_parts)
                if self._streaming_active:
                    self._streaming_active = False
                    return Message(
                        role="assistant",
                        content=content,
                        message_type=MessageType.STREAM_END,
                    )
                return Message(role="assistant", content=content)
            return None

        if isinstance(event, SystemMessage):
            if event.subtype == "init":
                return None
            return Message(
                role="system",
                content=f"[{event.subtype}]",
                metadata=event.data if hasattr(event, "data") else {},
            )

        if isinstance(event, ResultMessage):
            if event.is_error:
                return Message(
                    role="system",
                    content=f"[Error: {event.result}]",
                    metadata={"error": True},
                )
            return None

        # Handle legacy/generic event types
        if hasattr(event, "type"):
            if event.type == "text":
                return Message(role="assistant", content=event.text)
            elif event.type == "tool_use":
                return Message(
                    role="tool",
                    content=f"[Tool: {event.name}]",
                    metadata={"tool": event.name, "input": getattr(event, "input", {})},
                )

        # Fallback for string events
        if isinstance(event, str):
            return Message(role="assistant", content=event)

        return None

    def _parse_stream_event(self, raw_event: Any) -> Message | None:
        """Parse raw Anthropic stream event into a Message.

        Handles content_block_start, content_block_delta, content_block_stop.

        Args:
            raw_event: Raw Anthropic API stream event.

        Returns:
            Message with appropriate streaming type, or None.
        """
        event_type = getattr(raw_event, "type", None)

        if event_type == "content_block_start":
            # Start of a content block
            content_block = getattr(raw_event, "content_block", None)
            if content_block and getattr(content_block, "type", None) == "text":
                self._streaming_active = True
                return Message(
                    role="assistant",
                    content="",
                    message_type=MessageType.STREAM_START,
                )
            return None

        elif event_type == "content_block_delta":
            # Token/chunk during streaming
            delta = getattr(raw_event, "delta", None)
            if delta and getattr(delta, "type", None) == "text_delta":
                text = getattr(delta, "text", "")
                if text:
                    return Message(
                        role="assistant",
                        content=text,
                        message_type=MessageType.STREAM_DELTA,
                    )
            return None

        elif event_type == "content_block_stop":
            # End of content block (but message may continue)
            return None  # Wait for AssistantMessage for final content

        elif event_type == "message_start":
            # Message starting - could emit a signal
            return None

        elif event_type == "message_delta":
            # Message metadata update (stop_reason, etc.)
            return None

        elif event_type == "message_stop":
            # Message complete
            if self._streaming_active:
                self._streaming_active = False
                return Message(
                    role="assistant",
                    content="",
                    message_type=MessageType.STREAM_END,
                )
            return None

        return None

    @classmethod
    def check_auth(cls) -> tuple[bool, str]:
        """Check if Claude SDK authentication is configured.

        Returns:
            Tuple of (is_authenticated, status_message).
        """
        if not _check_sdk():
            return False, "Claude SDK not installed"

        auth_file = Path.home() / ".claude" / ".credentials.json"
        if auth_file.exists():
            return True, "Authenticated"
        return False, "Not authenticated. Run 'claude' CLI to login via browser."

    @classmethod
    def available_models(cls, config: BackendConfig | None = None) -> list[str]:
        """List available Claude models.

        Returns:
            List of model identifiers.
        """
        return cls.MODELS.copy()

    @classmethod
    def default_config(cls) -> BackendConfig:
        """Get default configuration for Claude backend.

        Returns:
            BackendConfig with Claude defaults.
        """
        return BackendConfig(
            model="claude-sonnet-4-20250514",
            max_turns=50,
        )
