"""
Exception hierarchy for SPARQLMojo ORM.
"""


class SPARQLMojoError(Exception):
    """Base exception for all SPARQLMojo errors."""

    pass


class ConfigurationError(SPARQLMojoError):
    """Raised when configuration is invalid or missing."""

    pass


class ValidationError(SPARQLMojoError):
    """Raised when data validation fails."""

    pass


class ModelError(SPARQLMojoError):
    """Base exception for model-related errors."""

    pass


class FieldError(ModelError):
    """Raised when field operation fails."""

    pass


class UnknownFieldError(FieldError):
    """Raised when accessing undefined field on model."""

    def __init__(self, model_class: type, field_name: str):
        self.model_class = model_class
        self.field_name = field_name
        # Use _rdf_fields instead of _fields to match our model implementation
        rdf_fields = getattr(model_class, "_rdf_fields", {})
        available_fields = list(rdf_fields.keys())
        super().__init__(
            f"Field '{field_name}' is not defined on model "
            f"'{model_class.__name__}'. "
            f"Available fields: {', '.join(available_fields)}"
        )


class RequiredFieldError(FieldError):
    """Raised when required field is missing."""

    pass


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

    pass


class ConnectionError(SessionError):
    """Raised when connection to SPARQL endpoint fails."""

    pass


class QueryError(SessionError):
    """Raised when SPARQL query execution fails."""

    def __init__(self, message: str, query: str | None = None):
        self.query = query
        if query:
            super().__init__(f"{message}\n\nQuery:\n{query}")
        else:
            super().__init__(message)


class SPARQLSyntaxError(QueryError):
    """Raised when generated SPARQL has syntax errors."""

    pass


class IRIError(ValidationError):
    """Raised when IRI validation fails."""

    def __init__(self, iri: str, reason: str):
        self.iri = iri
        super().__init__(f"Invalid IRI '{iri}': {reason}")


class SchemaValidationError(ValidationError):
    """Base exception for schema validation errors."""

    pass


class DomainConstraintError(SchemaValidationError):
    """Property domain doesn't match model's rdf_type."""

    def __init__(
        self,
        field_name: str,
        predicate: str,
        model_type: str | None,
        expected_domains: frozenset[str],
    ):
        self.field_name = field_name
        self.predicate = predicate
        self.model_type = model_type
        self.expected_domains = expected_domains
        domains_str = ", ".join(sorted(expected_domains))
        super().__init__(
            f"Domain constraint violation on field '{field_name}': "
            f"predicate '{predicate}' expects domain [{domains_str}], "
            f"but model has rdf_type '{model_type}'. "
            f"Consider using a predicate valid for '{model_type}'."
        )


class RangeTypeMismatchError(SchemaValidationError):
    """Python field type doesn't match property range."""

    def __init__(
        self,
        field_name: str,
        predicate: str,
        python_type: str,
        expected_ranges: frozenset[str],
    ):
        self.field_name = field_name
        self.predicate = predicate
        self.python_type = python_type
        self.expected_ranges = expected_ranges
        ranges_str = ", ".join(sorted(expected_ranges))
        super().__init__(
            f"Range type mismatch on field '{field_name}': "
            f"Python type '{python_type}' is not compatible with "
            f"predicate '{predicate}' range [{ranges_str}]."
        )


class CardinalityViolationError(SchemaValidationError):
    """Field cardinality doesn't match owl:FunctionalProperty."""

    def __init__(
        self,
        field_name: str,
        predicate: str,
        is_functional: bool,
        is_collection_field: bool,
    ):
        self.field_name = field_name
        self.predicate = predicate
        self.is_functional = is_functional
        self.is_collection_field = is_collection_field

        if is_functional and is_collection_field:
            msg = (
                f"Cardinality violation on field '{field_name}': "
                f"predicate '{predicate}' is owl:FunctionalProperty (single value), "
                f"but field is a collection type. Use a single-value field."
            )
        else:
            msg = (
                f"Cardinality warning on field '{field_name}': "
                f"predicate '{predicate}' is not owl:FunctionalProperty, "
                f"but field is single-valued. Consider using a collection field."
            )
        super().__init__(msg)


class UnknownPropertyWarning(UserWarning):
    """Property not found in registered ontologies."""

    pass
