"""
Standardized error handling for pycharter-wiki.

Exception hierarchy:
- PyCharterWikiError: Base for all pycharter-wiki exceptions
- OntologyError: Ontology loading/parsing failures
- OntologyValidationError: Structural validation failures
- VersioningError: Version control operations
- MergeConflictError: Merge conflicts
- CollaborationError: Workflow operations
- GraphError: Knowledge graph operations
- IngestionError: Import operations

Error handling modes:
- STRICT: Raise exceptions immediately on errors
- LENIENT: Log warnings and continue (best effort)
- COLLECT: Collect errors and return them with results
"""

from __future__ import annotations

import logging
import warnings
from dataclasses import dataclass, field
from enum import Enum
from typing import Any

logger = logging.getLogger(__name__)

# =============================================================================
# EXCEPTION HIERARCHY
# =============================================================================


class PyCharterWikiError(Exception):
    """
    Base exception for all pycharter-wiki errors.

    Catch this to handle any pycharter-wiki-related failure without depending on
    specific exception types. Subclasses provide more specific handling.

    Example:
        try:
            artifact = parse_ontology_file("ontology.yaml")
        except PyCharterWikiError as e:
            logger.error("Ontology operation failed: %s", e)
    """

    pass


class OntologyError(PyCharterWikiError):
    """
    Raised when an ontology cannot be loaded or parsed.

    Use for: missing files, invalid YAML, I/O errors during ontology load.
    """

    def __init__(self, message: str, path: str | None = None):
        self.path = path
        super().__init__(message + (f" (path: {path})" if path else ""))


class OntologyValidationError(PyCharterWikiError):
    """
    Raised when an ontology fails structural validation.

    Use for: missing required fields, invalid structure, circular references.
    May include a list of validation errors.
    """

    def __init__(
        self,
        message: str,
        errors: list[Transformer] | None = None,
    ):
        self.message = message
        self.errors = errors or []
        super().__init__(self._format_message())

    def _format_message(self) -> str:
        parts = [self.message]
        if self.errors:
            parts.append("\nValidation errors:")
            for i, error in enumerate(self.errors[:10], 1):
                path = error.get("path", "")
                msg = error.get("message", "Unknown error")
                parts.append(f"  {i}. {path}: {msg}" if path else f"  {i}. {msg}")
            if len(self.errors) > 10:
                parts.append(f"  ... and {len(self.errors) - 10} more errors")
        return "\n".join(parts)


class VersioningError(PyCharterWikiError):
    """
    Raised when a version control operation fails.

    Use for: invalid version references, branch operations, history queries.
    """

    pass


class MergeConflictError(VersioningError):
    """
    Raised when a merge operation encounters conflicts.

    Use for: conflicting edits to the same field in different branches.
    """

    def __init__(self, message: str, conflicts: list[dict] | None = None):
        self.conflicts = conflicts or []
        super().__init__(message)


class CollaborationError(PyCharterWikiError):
    """
    Raised when a collaboration workflow operation fails.

    Use for: proposal creation/review failures, permission denials.
    """

    pass


class GraphError(PyCharterWikiError):
    """
    Raised when a knowledge graph operation fails.

    Use for: query failures, invalid traversals, missing concepts.
    """

    pass


class IngestionError(PyCharterWikiError):
    """
    Raised when an import operation fails.

    Use for: invalid contract directories, missing ontology files, materialization failures.
    """

    pass


# =============================================================================
# ERROR MODES
# =============================================================================


class ErrorMode(Enum):
    """Error handling mode for pycharter-wiki operations."""

    STRICT = "strict"
    """Raise exceptions immediately on any error."""

    LENIENT = "lenient"
    """Log warnings and continue with best effort."""

    COLLECT = "collect"
    """Collect errors and return them with results."""


@dataclass
class ErrorContext:
    """
    Context for tracking errors during operations.

    Use with ErrorMode.COLLECT to gather errors without stopping.
    """

    mode: ErrorMode = ErrorMode.LENIENT
    errors: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)

    def handle_error(
        self,
        message: str,
        exception: Exception | None = None,
        category: str = "error",
    ) -> None:
        """
        Handle an error according to the current mode.

        Args:
            message: Error message
            exception: Optional exception that caused the error
            category: Error category for logging
        """
        full_message = f"{category}: {message}"
        if exception:
            full_message += f" ({type(exception).__name__}: {exception})"

        if self.mode == ErrorMode.STRICT:
            if exception:
                raise type(exception)(full_message) from exception
            raise ValueError(full_message)

        elif self.mode == ErrorMode.LENIENT:
            warnings.warn(full_message)
            logger.warning(full_message)
            self.warnings.append(full_message)

        elif self.mode == ErrorMode.COLLECT:
            self.errors.append(full_message)
            logger.warning(full_message)

    def handle_warning(self, message: str, category: str = "warning") -> None:
        """
        Handle a warning (non-fatal issue).

        Args:
            message: Warning message
            category: Warning category
        """
        full_message = f"{category}: {message}"
        warnings.warn(full_message)
        logger.warning(full_message)
        self.warnings.append(full_message)

    @property
    def has_errors(self) -> bool:
        """Whether any errors were collected."""
        return len(self.errors) > 0

    def raise_if_errors(self) -> None:
        """Raise ValueError if any errors were collected."""
        if self.errors:
            raise ValueError(f"Errors occurred: {'; '.join(self.errors)}")

    def clear(self) -> None:
        """Clear all errors and warnings."""
        self.errors.clear()
        self.warnings.clear()


# Default error context (lenient by default)
_default_context = ErrorContext(mode=ErrorMode.LENIENT)


def get_error_context() -> ErrorContext:
    """Get the default error context."""
    return _default_context


def set_error_mode(mode: ErrorMode) -> None:
    """
    Set the default error handling mode.

    Args:
        mode: Error mode to use
    """
    _default_context.mode = mode


def handle_error(
    message: str,
    exception: Exception | None = None,
    category: str = "error",
    context: ErrorContext | None = None,
) -> None:
    """
    Handle an error using the specified or default context.

    Args:
        message: Error message
        exception: Optional exception that caused the error
        category: Error category
        context: Optional error context (uses default if not provided)
    """
    ctx = context or _default_context
    ctx.handle_error(message, exception, category)


def handle_warning(
    message: str,
    category: str = "warning",
    context: ErrorContext | None = None,
) -> None:
    """
    Handle a warning using the specified or default context.

    Args:
        message: Warning message
        category: Warning category
        context: Optional error context (uses default if not provided)
    """
    ctx = context or _default_context
    ctx.handle_warning(message, category)


class StrictMode:
    """
    Context manager for temporarily enabling strict mode.

    Example:
        >>> with StrictMode():
        ...     # Errors will raise exceptions
        ...     validate_ontology(data)
    """

    def __init__(self):
        self._previous_mode: ErrorMode | None = None

    def __enter__(self):
        self._previous_mode = _default_context.mode
        _default_context.mode = ErrorMode.STRICT
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._previous_mode is not None:
            _default_context.mode = self._previous_mode
        return False


class LenientMode:
    """
    Context manager for temporarily enabling lenient mode.

    Example:
        >>> with LenientMode():
        ...     # Errors will be logged as warnings
        ...     validate_ontology(data)
    """

    def __init__(self):
        self._previous_mode: ErrorMode | None = None

    def __enter__(self):
        self._previous_mode = _default_context.mode
        _default_context.mode = ErrorMode.LENIENT
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._previous_mode is not None:
            _default_context.mode = self._previous_mode
        return False
