"""
Standardized exception hierarchy for data access operations.

Provides structured error handling with context, operation tracking,
and consistent error reporting across all data access layers.
"""

import traceback
from typing import Optional, Dict, Any, List
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum


class ErrorSeverity(Enum):
    """Severity levels for data access errors."""
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class OperationType(Enum):
    """Types of data access operations."""
    READ = "read"
    WRITE = "write"
    UPDATE = "update"
    DELETE = "delete"
    BATCH = "batch"
    TRANSACTION = "transaction"


@dataclass
class ErrorContext:
    """Context information for data access errors."""
    operation_id: str
    operation_type: OperationType
    resource_type: str
    resource_id: Optional[str] = None
    user_id: Optional[str] = None
    timestamp: datetime = field(default_factory=datetime.utcnow)
    request_data: Optional[Dict[str, Any]] = None
    metadata: Dict[str, Any] = field(default_factory=dict)


class DataAccessError(Exception):
    """
    Base exception for all data access errors.

    Provides structured error context, operation tracking,
    and debugging information for data access failures.
    """

    def __init__(
        self,
        message: str,
        context: Optional[ErrorContext] = None,
        severity: ErrorSeverity = ErrorSeverity.MEDIUM,
        cause: Optional[Exception] = None,
        recovery_suggestions: Optional[List[str]] = None
    ):
        super().__init__(message)
        self.message = message
        self.context = context
        self.severity = severity
        self.cause = cause
        self.recovery_suggestions = recovery_suggestions or []
        self.traceback_info = traceback.format_exc()

    def to_dict(self) -> Dict[str, Any]:
        """Convert error to dictionary for logging/serialization."""
        return {
            "error_type": self.__class__.__name__,
            "message": self.message,
            "severity": self.severity.value,
            "context": {
                "operation_id": self.context.operation_id if self.context else None,
                "operation_type": self.context.operation_type.value if self.context else None,
                "resource_type": self.context.resource_type if self.context else None,
                "resource_id": self.context.resource_id if self.context else None,
                "timestamp": self.context.timestamp.isoformat() if self.context else None,
                "metadata": self.context.metadata if self.context else {}
            },
            "cause": str(self.cause) if self.cause else None,
            "recovery_suggestions": self.recovery_suggestions,
            "traceback": self.traceback_info
        }

    def __str__(self) -> str:
        """Provide detailed string representation."""
        parts = [f"{self.__class__.__name__}: {self.message}"]

        if self.context:
            parts.append(f"Operation: {self.context.operation_type.value}")
            parts.append(f"Resource: {self.context.resource_type}")
            if self.context.resource_id:
                parts.append(f"ID: {self.context.resource_id}")

        if self.severity != ErrorSeverity.MEDIUM:
            parts.append(f"Severity: {self.severity.value}")

        if self.recovery_suggestions:
            parts.append(f"Suggestions: {', '.join(self.recovery_suggestions)}")

        return " | ".join(parts)


# Repository-specific errors

class RepositoryError(DataAccessError):
    """Base error for repository operations."""
    pass


class ResourceNotFoundError(RepositoryError):
    """Resource was not found in the data store."""

    def __init__(self, resource_type: str, resource_id: str, context: Optional[ErrorContext] = None):
        message = f"{resource_type} with ID '{resource_id}' not found"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.LOW,
            recovery_suggestions=[
                "Verify the resource ID is correct",
                "Check if the resource was deleted",
                "Refresh your data and try again"
            ]
        )
        self.resource_type = resource_type
        self.resource_id = resource_id


class DuplicateResourceError(RepositoryError):
    """Attempted to create a resource that already exists."""

    def __init__(self, resource_type: str, identifier: str, context: Optional[ErrorContext] = None):
        message = f"{resource_type} with identifier '{identifier}' already exists"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.LOW,
            recovery_suggestions=[
                "Use update operation instead of create",
                "Check for existing resources before creating",
                "Use upsert operation if available"
            ]
        )
        self.resource_type = resource_type
        self.identifier = identifier


class ValidationError(RepositoryError):
    """Data validation failed before repository operation."""

    def __init__(self, field: str, value: Any, rule: str, context: Optional[ErrorContext] = None):
        message = f"Validation failed for field '{field}': {rule}"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.MEDIUM,
            recovery_suggestions=[
                f"Correct the value for field '{field}'",
                "Review the validation rules",
                "Check the data format requirements"
            ]
        )
        self.field = field
        self.value = value
        self.rule = rule


class ConcurrencyError(RepositoryError):
    """Concurrent modification detected."""

    def __init__(self, resource_type: str, resource_id: str, context: Optional[ErrorContext] = None):
        message = f"Concurrent modification detected for {resource_type} '{resource_id}'"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.HIGH,
            recovery_suggestions=[
                "Retry the operation with fresh data",
                "Implement optimistic locking",
                "Use atomic operations where possible"
            ]
        )
        self.resource_type = resource_type
        self.resource_id = resource_id


# External service errors

class ExternalServiceError(DataAccessError):
    """Base error for external service interactions."""
    pass


class GiteaApiError(ExternalServiceError):
    """Error communicating with Gitea API."""

    def __init__(
        self,
        status_code: int,
        response_body: str,
        endpoint: str,
        context: Optional[ErrorContext] = None
    ):
        message = f"Gitea API error {status_code} at {endpoint}: {response_body}"
        severity = self._determine_severity(status_code)
        super().__init__(
            message=message,
            context=context,
            severity=severity,
            recovery_suggestions=self._get_recovery_suggestions(status_code)
        )
        self.status_code = status_code
        self.response_body = response_body
        self.endpoint = endpoint

    def _determine_severity(self, status_code: int) -> ErrorSeverity:
        """Determine error severity based on HTTP status code."""
        if status_code >= 500:
            return ErrorSeverity.HIGH
        elif status_code == 429:  # Rate limited
            return ErrorSeverity.MEDIUM
        elif status_code >= 400:
            return ErrorSeverity.LOW
        else:
            return ErrorSeverity.MEDIUM

    def _get_recovery_suggestions(self, status_code: int) -> List[str]:
        """Get recovery suggestions based on HTTP status code."""
        if status_code == 401:
            return ["Check API token is valid", "Verify authentication configuration"]
        elif status_code == 403:
            return ["Check API permissions", "Verify token has required scopes"]
        elif status_code == 404:
            return ["Verify the endpoint URL", "Check if the resource exists"]
        elif status_code == 429:
            return ["Implement rate limiting", "Wait before retrying", "Use exponential backoff"]
        elif status_code >= 500:
            return ["Retry the request", "Check Gitea service status", "Contact system administrator"]
        else:
            return ["Check request parameters", "Review API documentation"]


class NetworkError(ExternalServiceError):
    """Network connectivity error."""

    def __init__(self, operation: str, cause: Exception, context: Optional[ErrorContext] = None):
        message = f"Network error during {operation}: {str(cause)}"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.HIGH,
            cause=cause,
            recovery_suggestions=[
                "Check network connectivity",
                "Verify service endpoints are reachable",
                "Retry with exponential backoff",
                "Check firewall and proxy settings"
            ]
        )
        self.operation = operation


# Database-specific errors

class DatabaseError(DataAccessError):
    """Base error for database operations."""
    pass


class ConnectionError(DatabaseError):
    """Database connection error."""

    def __init__(self, database: str, cause: Exception, context: Optional[ErrorContext] = None):
        message = f"Failed to connect to database '{database}': {str(cause)}"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.CRITICAL,
            cause=cause,
            recovery_suggestions=[
                "Check database is running",
                "Verify connection string",
                "Check database permissions",
                "Verify network connectivity"
            ]
        )
        self.database = database


class TransactionError(DatabaseError):
    """Database transaction error."""

    def __init__(self, operation: str, cause: Exception, context: Optional[ErrorContext] = None):
        message = f"Transaction failed during {operation}: {str(cause)}"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.HIGH,
            cause=cause,
            recovery_suggestions=[
                "Retry the entire transaction",
                "Check for deadlocks",
                "Verify data constraints",
                "Review transaction isolation level"
            ]
        )
        self.operation = operation


class QueryError(DatabaseError):
    """Database query execution error."""

    def __init__(self, query: str, parameters: Dict[str, Any], cause: Exception, context: Optional[ErrorContext] = None):
        message = f"Query execution failed: {str(cause)}"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.MEDIUM,
            cause=cause,
            recovery_suggestions=[
                "Check query syntax",
                "Verify parameter types",
                "Check table/column names",
                "Review database schema"
            ]
        )
        self.query = query
        self.parameters = parameters


# Cache-specific errors

class CacheError(DataAccessError):
    """Base error for cache operations."""
    pass


class CacheMissError(CacheError):
    """Requested item not found in cache."""

    def __init__(self, cache_key: str, context: Optional[ErrorContext] = None):
        message = f"Cache miss for key '{cache_key}'"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.LOW,
            recovery_suggestions=[
                "Load data from primary source",
                "Check cache key format",
                "Verify cache is populated"
            ]
        )
        self.cache_key = cache_key


class CacheInvalidationError(CacheError):
    """Failed to invalidate cache entries."""

    def __init__(self, pattern: str, cause: Exception, context: Optional[ErrorContext] = None):
        message = f"Failed to invalidate cache pattern '{pattern}': {str(cause)}"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.MEDIUM,
            cause=cause,
            recovery_suggestions=[
                "Retry cache invalidation",
                "Clear entire cache if needed",
                "Check cache connection",
                "Monitor cache consistency"
            ]
        )
        self.pattern = pattern


# Configuration errors

class ConfigurationError(DataAccessError):
    """Configuration-related error."""

    def __init__(self, setting: str, value: Any, context: Optional[ErrorContext] = None):
        message = f"Invalid configuration for '{setting}': {value}"
        super().__init__(
            message=message,
            context=context,
            severity=ErrorSeverity.CRITICAL,
            recovery_suggestions=[
                f"Check configuration for '{setting}'",
                "Review environment variables",
                "Verify configuration file format",
                "Check default values"
            ]
        )
        self.setting = setting
        self.value = value