"""
Domain Interfaces

Abstract interfaces (protocols) that define contracts between layers.
These enable dependency injection and testability.

v2.0: Updated with event callback system for tool call visibility.
"""

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Callable

from honeydb_ai.domain.models import (
    Config,
    Event,
    LLMResponse,
    Message,
    Session,
    SessionMetadata,
)


# Storage Interfaces
class SessionStore(ABC):
    """
    Abstract interface for session persistence.

    v2.0 Critical: Implementations must use file locking and atomic writes.
    """

    @abstractmethod
    def save(self, session: Session) -> None:
        """
        Save a session to storage.

        v2.0: Must implement atomic writes with file locking to prevent corruption.

        Raises:
            SessionSaveError: If save fails
            SessionTooLargeError: If session exceeds size limits
        """
        pass

    @abstractmethod
    def load(self, session_id: str) -> Session:
        """
        Load a session from storage.

        v2.0: Must handle corrupted files and attempt backup restoration.

        Raises:
            SessionNotFoundError: If session doesn't exist
            SessionLoadError: If session cannot be loaded
            SessionCorruptedError: If session file is corrupted
        """
        pass

    @abstractmethod
    def delete(self, session_id: str) -> None:
        """
        Delete a session from storage.

        Raises:
            SessionNotFoundError: If session doesn't exist
        """
        pass

    @abstractmethod
    def list_all(self) -> list[SessionMetadata]:
        """
        List all sessions.

        v2.0 Critical: Should use persistent metadata cache for performance.

        Returns:
            List of session metadata, sorted by updated_at (most recent first)
        """
        pass

    @abstractmethod
    def exists(self, session_id: str) -> bool:
        """Check if a session exists."""
        pass

    @abstractmethod
    def get_storage_dir(self) -> Path:
        """Get the storage directory path."""
        pass


class ConfigStore(ABC):
    """Abstract interface for configuration persistence."""

    @abstractmethod
    def load(self) -> Config:
        """
        Load configuration from storage.

        Returns:
            Config object with defaults for missing values

        Raises:
            ConfigurationError: If configuration is invalid
        """
        pass

    @abstractmethod
    def save(self, config: Config) -> None:
        """
        Save configuration to storage.

        Raises:
            StorageError: If save fails
        """
        pass

    @abstractmethod
    def exists(self) -> bool:
        """Check if configuration file exists."""
        pass

    @abstractmethod
    def get_config_path(self) -> Path:
        """Get the configuration file path."""
        pass


# LLM Provider Interface
class LLMProviderAdapter(ABC):
    """
    Abstract interface for LLM provider implementations.

    v2.0 Critical: Added event_callback for real-time tool call visibility.
    v2.0: Added stream parameter (future-ready for Phase 4).
    """

    @abstractmethod
    async def send_message(
        self,
        messages: list[Message],
        model: str | None = None,
        temperature: float | None = None,
        max_tokens: int | None = None,
        stream: bool = False,
        event_callback: Callable[[Event], None] | None = None,
    ) -> LLMResponse:
        """
        Send messages to the LLM provider and get a response.

        Args:
            messages: List of messages in the conversation
            model: Optional model override
            temperature: Optional temperature override
            max_tokens: Optional max tokens override
            stream: Whether to stream the response (Phase 1: False, Phase 4: True)
            event_callback: Optional callback for emitting events (tool calls, etc.)

        Returns:
            LLMResponse with the assistant's response

        Raises:
            LLMProviderError: If the provider returns an error
            LLMAPIError: If the API call fails
            LLMRateLimitError: If rate limit is exceeded
            LLMRequestCancelledError: If user cancels the request (Ctrl+C)

        v2.0 Critical: Implementations must emit events via event_callback:
        - "request_started" when request begins
        - "tool_call" when an MCP tool is invoked
        - "tool_result" when an MCP tool returns
        - "response_received" when final response is ready
        """
        pass

    @abstractmethod
    def validate_api_key(self) -> bool:
        """
        Validate that the API key is configured and appears valid.

        Returns:
            True if API key is present and non-empty
        """
        pass

    @abstractmethod
    def get_provider_name(self) -> str:
        """Get the provider name (e.g., 'openai', 'anthropic', 'gemini')."""
        pass

    @abstractmethod
    def get_available_models(self) -> list[str]:
        """
        Get list of available models for this provider.

        Returns:
            List of model identifiers
        """
        pass


# UI Interfaces
class Console(ABC):
    """
    Abstract interface for console/terminal operations.

    v2.0 Critical: Must implement terminal capability detection.
    """

    @abstractmethod
    def print(self, text: str, style: str | None = None) -> None:
        """Print text to console with optional styling."""
        pass

    @abstractmethod
    def print_markdown(self, markdown: str) -> None:
        """
        Render and print markdown content.

        v2.0: Should chunk large content to avoid blocking UI.
        """
        pass

    @abstractmethod
    def print_error(self, message: str) -> None:
        """Print an error message."""
        pass

    @abstractmethod
    def print_success(self, message: str) -> None:
        """Print a success message."""
        pass

    @abstractmethod
    def print_warning(self, message: str) -> None:
        """Print a warning message."""
        pass

    @abstractmethod
    def print_event(self, event: Event) -> None:
        """
        Print an event (e.g., tool call notification).

        v2.0 Critical: Real-time display of MCP tool invocations.
        """
        pass

    @abstractmethod
    def prompt(self, message: str, default: str | None = None) -> str:
        """Prompt user for input."""
        pass

    @abstractmethod
    def confirm(self, message: str, default: bool = False) -> bool:
        """Prompt user for yes/no confirmation."""
        pass

    @abstractmethod
    def detect_terminal_capabilities(self) -> dict[str, bool]:
        """
        Detect terminal capabilities.

        v2.0 Critical: Must detect dark/light mode and adjust colors accordingly.

        Returns:
            Dictionary with capabilities:
            - "color_support": True if terminal supports colors
            - "unicode_support": True if terminal supports Unicode
            - "dark_mode": True if terminal has dark background
        """
        pass
