# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Familiar Exception Hierarchy

Provides structured exceptions for error handling throughout the framework.
All exceptions inherit from FamiliarError for easy catching of framework errors.

Usage:
    from familiar.core.exceptions import (
        SecurityError, AuthorizationError, ProviderError
    )

    try:
        agent.chat(...)
    except AuthorizationError as e:
        logger.warning(f"Access denied: {e}")
    except ProviderError as e:
        logger.error(f"LLM failed: {e}")
"""

from typing import Any, Dict, Optional


class FamiliarError(Exception):
    """
    Base exception for all Familiar errors.

    Attributes:
        message: Human-readable error description
        details: Optional dict with additional context
    """

    def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
        super().__init__(message)
        self.message = message
        self.details = details or {}

    def __str__(self):
        if self.details:
            return f"{self.message} | Details: {self.details}"
        return self.message


# ============================================================
# CONFIGURATION ERRORS
# ============================================================


class ConfigurationError(FamiliarError):
    """
    Invalid or missing configuration.

    Raised when:
    - Config file is malformed
    - Required config values are missing
    - Config values fail validation
    """

    pass


class SkillConfigError(ConfigurationError):
    """Invalid skill configuration."""

    pass


class ProviderConfigError(ConfigurationError):
    """Invalid LLM provider configuration."""

    pass


# ============================================================
# SECURITY ERRORS
# ============================================================


class SecurityError(FamiliarError):
    """
    Base class for security-related errors.

    All security violations should inherit from this class.
    """

    pass


class AuthenticationError(SecurityError):
    """
    Authentication failed.

    Raised when:
    - Invalid credentials
    - Session expired
    - Token invalid
    """

    pass


class AuthorizationError(SecurityError):
    """
    Insufficient permissions.

    Raised when user lacks required capabilities.

    Attributes:
        required_capability: The capability that was needed
        user_trust_level: User's current trust level
    """

    def __init__(
        self,
        message: str,
        required_capability: Optional[str] = None,
        user_trust_level: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if required_capability:
            details["required_capability"] = required_capability
        if user_trust_level:
            details["user_trust_level"] = user_trust_level
        super().__init__(message, details)
        self.required_capability = required_capability
        self.user_trust_level = user_trust_level


class RateLimitError(SecurityError):
    """
    Rate limit exceeded.

    Attributes:
        limit_type: Type of limit hit (requests, tokens, cost)
        current_value: Current usage value
        limit_value: The limit that was exceeded
        retry_after: Seconds until retry is allowed (if known)
    """

    def __init__(
        self,
        message: str,
        limit_type: str = "requests",
        current_value: Optional[float] = None,
        limit_value: Optional[float] = None,
        retry_after: Optional[int] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        details.update(
            {
                "limit_type": limit_type,
                "current_value": current_value,
                "limit_value": limit_value,
                "retry_after": retry_after,
            }
        )
        super().__init__(message, details)
        self.limit_type = limit_type
        self.current_value = current_value
        self.limit_value = limit_value
        self.retry_after = retry_after


class BudgetExceededError(SecurityError):
    """
    Cost budget exceeded.

    Attributes:
        budget_type: daily, hourly, per_request
        spent: Amount spent
        budget: Budget limit
    """

    def __init__(
        self,
        message: str,
        budget_type: str = "daily",
        spent: Optional[float] = None,
        budget: Optional[float] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        details.update(
            {
                "budget_type": budget_type,
                "spent": spent,
                "budget": budget,
            }
        )
        super().__init__(message, details)
        self.budget_type = budget_type
        self.spent = spent
        self.budget = budget


class InputValidationError(SecurityError):
    """
    Input failed security validation.

    Raised when:
    - Shell command contains dangerous patterns
    - Path traversal detected
    - Prompt injection suspected
    """

    def __init__(
        self,
        message: str,
        input_type: str = "unknown",
        matched_pattern: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        details.update(
            {
                "input_type": input_type,
                "matched_pattern": matched_pattern,
            }
        )
        super().__init__(message, details)
        self.input_type = input_type
        self.matched_pattern = matched_pattern


# ============================================================
# PROVIDER ERRORS
# ============================================================


class ProviderError(FamiliarError):
    """
    Base class for LLM provider errors.

    Attributes:
        provider_name: Name of the provider (anthropic, openai, ollama)
    """

    def __init__(
        self,
        message: str,
        provider_name: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if provider_name:
            details["provider"] = provider_name
        super().__init__(message, details)
        self.provider_name = provider_name


class ProviderConnectionError(ProviderError):
    """
    Cannot connect to LLM provider.

    Network issues, service unavailable, etc.
    """

    pass


class ProviderAuthenticationError(ProviderError):
    """
    Provider authentication failed.

    Invalid API key, expired token, etc.
    """

    pass


class ProviderRateLimitError(ProviderError):
    """
    Provider rate limit hit.

    Different from our internal rate limiting - this is the
    provider (Anthropic, OpenAI) saying we're sending too many requests.

    Attributes:
        retry_after: Seconds to wait before retrying (if provided)
    """

    def __init__(
        self,
        message: str,
        provider_name: Optional[str] = None,
        retry_after: Optional[int] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if retry_after:
            details["retry_after"] = retry_after
        super().__init__(message, provider_name, details)
        self.retry_after = retry_after


class ProviderResponseError(ProviderError):
    """
    Invalid or unexpected response from provider.

    Malformed JSON, missing fields, etc.
    """

    pass


class ProviderContentFilterError(ProviderError):
    """
    Provider blocked content due to safety filters.
    """

    pass


# ============================================================
# TOOL ERRORS
# ============================================================


class ToolError(FamiliarError):
    """
    Base class for tool-related errors.

    Attributes:
        tool_name: Name of the tool that failed
    """

    def __init__(
        self,
        message: str,
        tool_name: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if tool_name:
            details["tool"] = tool_name
        super().__init__(message, details)
        self.tool_name = tool_name


class ToolNotFoundError(ToolError):
    """Tool is not registered in the registry."""

    pass


class ToolExecutionError(ToolError):
    """
    Tool failed during execution.

    The tool was found and started, but failed to complete.
    """

    pass


class ToolTimeoutError(ToolError):
    """Tool execution exceeded timeout."""

    pass


class ToolPermissionError(ToolError):
    """
    Tool requires capabilities the user doesn't have.

    Similar to AuthorizationError but specific to tool context.
    """

    def __init__(
        self,
        message: str,
        tool_name: Optional[str] = None,
        required_capabilities: Optional[list] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if required_capabilities:
            details["required_capabilities"] = required_capabilities
        super().__init__(message, tool_name, details)
        self.required_capabilities = required_capabilities or []


# ============================================================
# SESSION ERRORS
# ============================================================


class SessionError(FamiliarError):
    """Base class for session-related errors."""

    pass


class SessionExpiredError(SessionError):
    """Session has expired and needs re-authentication."""

    pass


class SessionCorruptedError(SessionError):
    """Session data is corrupted or invalid."""

    pass


class SessionValidationError(SessionError):
    """Session data failed schema validation."""

    def __init__(
        self,
        message: str,
        validation_errors: Optional[list] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if validation_errors:
            details["validation_errors"] = validation_errors
        super().__init__(message, details)
        self.validation_errors = validation_errors or []


# ============================================================
# SKILL ERRORS
# ============================================================


class SkillError(FamiliarError):
    """Base class for skill-related errors."""

    def __init__(
        self,
        message: str,
        skill_name: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if skill_name:
            details["skill"] = skill_name
        super().__init__(message, details)
        self.skill_name = skill_name


class SkillLoadError(SkillError):
    """Failed to load a skill."""

    pass


class SkillExecutionError(SkillError):
    """Skill execution failed."""

    pass


# ============================================================
# VALIDATION ERRORS
# ============================================================


class ValidationError(FamiliarError):
    """
    General input validation error.

    For non-security-related validation failures.
    """

    def __init__(
        self,
        message: str,
        field: Optional[str] = None,
        value: Optional[Any] = None,
        details: Optional[Dict[str, Any]] = None,
    ):
        details = details or {}
        if field:
            details["field"] = field
        if value is not None:
            details["value"] = str(value)[:100]  # Truncate for safety
        super().__init__(message, details)
        self.field = field
        self.value = value


# ============================================================
# EXPORTS
# ============================================================

__all__ = [
    # Base
    "FamiliarError",
    # Configuration
    "ConfigurationError",
    "SkillConfigError",
    "ProviderConfigError",
    # Security
    "SecurityError",
    "AuthenticationError",
    "AuthorizationError",
    "RateLimitError",
    "BudgetExceededError",
    "InputValidationError",
    # Provider
    "ProviderError",
    "ProviderConnectionError",
    "ProviderAuthenticationError",
    "ProviderRateLimitError",
    "ProviderResponseError",
    "ProviderContentFilterError",
    # Tool
    "ToolError",
    "ToolNotFoundError",
    "ToolExecutionError",
    "ToolTimeoutError",
    "ToolPermissionError",
    # Session
    "SessionError",
    "SessionExpiredError",
    "SessionCorruptedError",
    "SessionValidationError",
    # Skill
    "SkillError",
    "SkillLoadError",
    "SkillExecutionError",
    # Validation
    "ValidationError",
]
