"""Exception hierarchy for CleverAgents.

Based on ADR-005: Error Handling Hierarchy and Exception Strategy.
All exceptions inherit from CleverAgentsError and follow fail-fast principles.
"""

import os
from typing import Any


class CleverAgentsError(Exception):
    """Base exception for all CleverAgents errors."""

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


# Backward compatibility exception types for v2 code
class CleverAgentsException(CleverAgentsError):
    """Backward compatibility exception name for v2 code."""


class UnsafeConfigurationError(CleverAgentsError):
    """Unsafe configuration requires explicit opt-in."""


# Domain Exceptions
class DomainError(CleverAgentsError):
    """Base for domain/business logic errors."""

    pass


class ValidationError(DomainError):
    """Data validation failures."""

    pass


class ToolTypeMismatchError(DomainError):
    """Raised when a plain Tool is used where a Validation is required.

    The ``agents validation attach`` command only accepts entries whose
    ``tool_type`` discriminator is ``"validation"``.  Attempting to attach
    a plain Tool raises this error.

    Attributes:
        tool_name: The namespaced name of the offending tool.
        actual_type: The ``tool_type`` value found on the registered entry.
    """

    def __init__(
        self,
        tool_name: str,
        actual_type: str,
        details: dict[str, Any] | None = None,
    ) -> None:
        """Initialise with the offending tool name and its actual type.

        Args:
            tool_name: Namespaced name of the tool that was rejected.
            actual_type: The ``tool_type`` value found (e.g. ``"tool"``).
            details: Optional additional context.
        """
        super().__init__(
            f"'{tool_name}' is a plain Tool (tool_type='{actual_type}'), "
            "not a Validation. Only entries with tool_type='validation' "
            "may be attached via 'agents validation attach'.",
            details,
        )
        self.tool_name = tool_name
        self.actual_type = actual_type


class BusinessRuleViolation(DomainError):
    """Business rule violations."""

    pass


class ResourceNotFoundError(DomainError):
    """Requested resource doesn't exist."""

    def __init__(
        self,
        message: str | None = None,
        resource_type: str | None = None,
        resource_id: str | None = None,
        details: dict[str, Any] | None = None,
    ):
        """Initialize with resource information.

        Args:
            message: Error message (auto-generated if not provided)
            resource_type: Type of resource (e.g., 'project', 'plan')
            resource_id: Resource identifier
            details: Additional error context
        """
        if not message and resource_type and resource_id:
            message = f"{resource_type.capitalize()} '{resource_id}' not found"
        elif not message:
            message = "Resource not found"

        super().__init__(message, details)
        self.resource_type = resource_type
        self.resource_id = resource_id


# Alias for compatibility
NotFoundError = ResourceNotFoundError


class ResourceConflictError(DomainError):
    """Resource state conflict."""

    pass


class LockConflictError(BusinessRuleViolation):
    """Raised when a lock cannot be acquired due to a conflicting owner.

    Attributes:
        resource_type: The type of resource that is locked.
        resource_id: The identifier of the locked resource.
        owner_id: The owner that currently holds the lock.
    """

    def __init__(
        self,
        resource_type: str,
        resource_id: str,
        owner_id: str,
    ) -> None:
        """Initialize lock conflict error.

        Args:
            resource_type: The type of resource (e.g. 'plan', 'project').
            resource_id: The identifier of the locked resource.
            owner_id: The owner that currently holds the lock.
        """
        super().__init__(
            f"Cannot acquire lock on {resource_type}/{resource_id}: "
            f"held by owner '{owner_id}'"
        )
        self.resource_type = resource_type
        self.resource_id = resource_id
        self.owner_id = owner_id


class LockExpiredError(DomainError):
    """Raised when a lock operation targets an expired lock.

    Attributes:
        resource_type: The type of resource.
        resource_id: The identifier of the resource.
    """

    def __init__(self, resource_type: str, resource_id: str) -> None:
        """Initialize lock expired error.

        Args:
            resource_type: The type of resource.
            resource_id: The identifier of the resource.
        """
        super().__init__(f"Lock on {resource_type}/{resource_id} has expired")
        self.resource_type = resource_type
        self.resource_id = resource_id


# Infrastructure Exceptions
class InfrastructureError(CleverAgentsError):
    """Base for infrastructure errors."""

    pass


class DatabaseError(InfrastructureError):
    """Database operation failures."""

    pass


class MigrationNotApprovedError(DatabaseError):
    """Database migrations were declined by the user."""

    pass


class NetworkError(InfrastructureError):
    """Network communication failures."""

    pass


class ExternalServiceError(InfrastructureError):
    """External service failures."""

    pass


# Provider Exceptions
class ProviderError(CleverAgentsError):
    """Base for AI provider errors."""

    pass


class StreamRoutingError(CleverAgentsError):
    """Reactive stream routing failures."""

    pass


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

    def __init__(self, message: str, retry_after: int | None = None):
        """Initialize with optional retry_after seconds.

        Args:
            message: Error message
            retry_after: Number of seconds to wait before retrying
        """
        super().__init__(message)
        self.retry_after = retry_after


class ModelNotAvailableError(ProviderError):
    """Model not available or deprecated."""

    pass


class TokenLimitExceededError(ProviderError):
    """Context window exceeded."""

    pass


# Authentication/Authorization
class AuthenticationError(CleverAgentsError):
    """Authentication failures."""

    pass


class AuthorizationError(CleverAgentsError):
    """Authorization failures."""

    pass


# Configuration Errors
class ConfigurationError(CleverAgentsError):
    """Configuration issues."""

    pass


class MissingConfigurationError(ConfigurationError):
    """Required configuration missing."""

    pass


# File System Errors
class FileSystemError(CleverAgentsError):
    """File system operation failures."""

    def __init__(
        self,
        message: str,
        path: str | os.PathLike[str] | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        """Initialize with path information.

        Args:
            message: Error message
            path: Path that caused the error
            details: Additional error context
        """
        super().__init__(message, details)
        self.path = path


# Plan Errors
class PlanError(DomainError):
    """Plan operation failures."""

    pass


class DecisionPhaseViolationError(BusinessRuleViolation):
    """Raised when a decision type is invalid for the plan's current phase.

    Attributes:
        decision_type: The :class:`str` value of the attempted decision type.
        plan_phase: The :class:`str` value of the plan's current phase.
        allowed_types: Frozenset of :class:`str` values of decision types
            permitted in the current phase.
    """

    def __init__(
        self,
        decision_type: str,
        plan_phase: str,
        allowed_types: frozenset[str],
    ) -> None:
        sorted_allowed = sorted(allowed_types)
        super().__init__(
            f"Decision type '{decision_type}' is not permitted in the "
            f"'{plan_phase}' phase. Allowed types: {sorted_allowed}"
        )
        self.decision_type = decision_type
        self.plan_phase = plan_phase
        self.allowed_types = allowed_types


class ExecutionError(CleverAgentsError):
    """Execution failures for agents or graph nodes."""

    pass


__all__ = [
    "AuthenticationError",
    "AuthorizationError",
    "BusinessRuleViolation",
    "CleverAgentsError",
    "ConfigurationError",
    "DatabaseError",
    "DecisionPhaseViolationError",
    "DomainError",
    "ExecutionError",
    "ExternalServiceError",
    "FileSystemError",
    "InfrastructureError",
    "LockConflictError",
    "LockExpiredError",
    "MigrationNotApprovedError",
    "MissingConfigurationError",
    "ModelNotAvailableError",
    "NetworkError",
    "NotFoundError",
    "PlanError",
    "ProviderError",
    "RateLimitError",
    "ResourceConflictError",
    "ResourceNotFoundError",
    "StreamRoutingError",
    "TokenLimitExceededError",
    "ToolTypeMismatchError",
    "ValidationError",
]
