"""
Field types for IRI references and relationships.

This module provides field types for IRI-valued fields including:
- IRIField: Basic IRI references
- InverseField: Inverse property relationships
- ObjectPropertyField: Object properties with range constraints
- SubjectField: Special field for RDF subjects
"""

from typing import TYPE_CHECKING, Any, Callable, override

from ..security import validate_predicate_iri
from ..values import IRI
from .constants import _SUBJECT_FIELD_PREDICATE_MARKER
from .field_base import RDFFieldInfo
from .property_path import PropertyPath

if TYPE_CHECKING:
    from ..schema_registry import SchemaRegistry


class IRIField(RDFFieldInfo):
    """
    Field for IRI references to other resources.
    Values are serialized with angle brackets in SPARQL.
    """

    @override
    def _copy_kwargs(self) -> dict[str, Any]:
        """Return empty dict - IRIField has no extra attributes."""
        return {}

    @override
    def format_value(self, value: Any) -> str:
        """Format value as a SPARQL IRI reference with angle brackets.

        If value already has to_sparql(), delegates to it.
        Otherwise auto-wraps in IRI.
        """
        # Value types know how to format themselves
        if hasattr(value, "to_sparql"):
            result: str = value.to_sparql()
            return result

        # Auto-wrap plain values
        return IRI(str(value)).to_sparql()

    @override
    def format_value_for_filter(self, value: Any) -> str:
        """Format value for SPARQL FILTER expressions.

        If value already has to_sparql_filter(), delegates to it.
        Otherwise auto-wraps in IRI.
        """
        # Value types know how to format themselves for filters
        if hasattr(value, "to_sparql_filter"):
            result: str = value.to_sparql_filter()
            return result

        # Auto-wrap plain values
        return IRI(str(value)).to_sparql_filter()


class InverseField(IRIField):
    """
    Field for inverse property relationships using SPARQL property paths.

    InverseField automatically generates SPARQL patterns with the inverse operator (^)
    to traverse relationships in reverse. This is useful when you want to find
    subjects by traversing a predicate backwards.

    **Normal vs. Inverse Patterns**:
        - Normal: ``?s <predicate> ?o`` (finds objects given subject)
        - Inverse: ``?s ^<predicate> ?o`` (finds subjects given object)
        - Equivalent to: ``?o <predicate> ?s``

    **Schema-Aware Discovery**:
        By default (auto_discover=True), when a global SchemaRegistry is set,
        InverseField will use the named inverse property from owl:inverseOf
        definitions instead of the ^ operator. If no inverse is defined in
        the ontology, it falls back to the ^ operator.

        Set auto_discover=False to always use the ^ operator.

    Inherits from IRIField because inverse relationships point to IRI resources.

    Example:
        >>> from typing import Annotated
        >>> from sparqlmojo import Model, SubjectField, InverseField
        >>>
        >>> class Child(Model):
        ...     iri: Annotated[str, SubjectField()]
        ...     # Find parent by traversing parent->child relationship in reverse
        ...     parent: Annotated[str | None, InverseField(
        ...         "http://schema.org/children"
        ...     )] = None
        >>>
        >>> # If schema registry has owl:inverseOf for children -> parent:
        >>> # generates: ?s <http://schema.org/parent> ?parent .
        >>> # Otherwise falls back to: ?s ^<http://schema.org/children> ?parent .

    Example opting out of schema discovery:
        >>> class Child(Model):
        ...     iri: Annotated[str, SubjectField()]
        ...     # Always use ^ operator, even if schema has owl:inverseOf
        ...     parent: Annotated[str | None, InverseField(
        ...         "http://schema.org/children",
        ...         auto_discover=False
        ...     )] = None
        >>>
        >>> # Always generates: ?s ^<http://schema.org/children> ?parent .

    Args:
        predicate: The forward predicate IRI to invert (string or PropertyPath)
        auto_discover: If True (default), discover named inverse from schema
        required: Whether this field is required
        default: Default value if not provided
        validator: Optional validation function(s)

    See Also:
        - :class:`PropertyPath` for property path syntax documentation
        - :class:`IRIField` for base IRI field functionality
    """

    def __init__(
        self,
        predicate: "str | PropertyPath",
        auto_discover: bool = True,
        required: bool = False,
        default: Any = None,
        validator: Callable[[Any], None] | list[Callable[[Any], None]] | None = None,
        **kwargs: Any,
    ):
        """
        Initialize InverseField.

        Args:
            predicate: Forward predicate IRI to invert
            auto_discover: If True (default), discover named inverse from schema
            required: Whether this field is required
            default: Default value if not provided
            validator: Optional validation function(s)
            **kwargs: Additional keyword arguments passed to parent
        """
        # Store auto_discover flag and original predicate for later use
        self._auto_discover: bool = auto_discover
        self._forward_predicate: str | None = (
            str(predicate) if isinstance(predicate, str) else None
        )

        # Convert predicate to inverse PropertyPath
        if isinstance(predicate, PropertyPath):
            # Already a PropertyPath - use as-is (user may have custom path)
            inverse_path: PropertyPath = predicate
        else:
            # Create inverse PropertyPath from predicate IRI
            inverse_path = self._create_inverse_path(str(predicate))

        # Call parent with inverse path (do NOT pass auto_discover to parent)
        super().__init__(
            predicate=inverse_path,
            required=required,
            default=default,
            validator=validator,
            **kwargs,
        )

    def _create_inverse_path(self, predicate: str) -> PropertyPath:
        """
        Create inverse PropertyPath from predicate IRI.

        Wraps the predicate in the inverse operator (^) using PropertyPath.

        Args:
            predicate: The forward predicate IRI

        Returns:
            PropertyPath with inverse operator
        """
        # Format the predicate for use in property path
        formatted: str = PropertyPath.format_predicate(predicate)
        # Apply inverse operator
        return PropertyPath(f"^{formatted}")

    def discover_inverse_from_schema(self, schema_registry: "SchemaRegistry") -> None:
        """
        Populate inverse from schema registry if auto_discover enabled.

        If auto_discover=True and the schema registry has owl:inverseOf metadata
        for the forward predicate, this method updates the field to use the
        named inverse property instead of the ^ operator.

        Args:
            schema_registry: SchemaRegistry to query for inverse relationships

        Example:
            >>> from sparqlmojo import SchemaRegistry, PropertyInfo
            >>> registry = SchemaRegistry()
            >>> registry.register_property(PropertyInfo(
            ...     predicate_iri="http://schema.org/children",
            ...     inverse_of="http://schema.org/parent"
            ... ))
            >>> field = InverseField(
            ...     "http://schema.org/children",
            ...     auto_discover=True
            ... )
            >>> field.discover_inverse_from_schema(registry)
            >>> # Field now uses schema:parent instead of ^schema:children
        """
        if not self._auto_discover or not self._forward_predicate:
            return

        from ..schema_registry import SchemaRegistry as SchemaRegistryType

        if not isinstance(schema_registry, SchemaRegistryType):
            return

        prop_info = schema_registry.get_property(self._forward_predicate)
        if prop_info and prop_info.inverse_of:
            # Use the named inverse property from ontology
            self._property_path = None  # Clear property path
            # Store the inverse IRI directly as predicate
            # We need to validate it as a normal predicate IRI
            validate_predicate_iri(prop_info.inverse_of)
            self.predicate = prop_info.inverse_of

    @override
    def _copy_kwargs(self) -> dict[str, Any]:
        """Return auto_discover for copy(), merged with parent kwargs."""
        return {**super()._copy_kwargs(), "auto_discover": self._auto_discover}

    @override
    def copy(self) -> "InverseField":
        """Create a deep copy of this InverseField.

        Preserves both the auto_discover flag and the forward predicate.
        """
        # Create the copy using the forward predicate (not the PropertyPath)
        # so that _forward_predicate is preserved
        predicate_for_copy: str | PropertyPath = (
            self._forward_predicate
            if self._forward_predicate is not None
            else (
                self._property_path
                if self._property_path is not None
                else self.predicate
            )
        )

        new_instance: InverseField = type(self)(
            predicate=predicate_for_copy,
            required=self.required,
            default=self.default,
            validator=self.validators,
            auto_discover=self._auto_discover,
        )

        # Copy over extra_kwargs if any
        new_instance.extra_kwargs = dict(self.extra_kwargs)

        return new_instance


class ObjectPropertyField(IRIField):
    """
    Field for object properties linking to other RDF resources.

    Inherits from IRIField to automatically handle IRI formatting in SPARQL
    queries. This ensures filter operations produce correct IRI syntax
    (``<http://...>``) rather than string literals (``"http://..."``), which is
    essential for SPARQL query correctness when filtering on object properties.

    The inheritance relationship also simplifies type checking: any code that
    checks ``isinstance(field, IRIField)`` will correctly identify
    ObjectPropertyField as an IRI-valued field, enabling consistent handling
    across all IRI field types.

    The range_ parameter specifies the RDF class (rdfs:range) that objects
    of this property should have. For example, if 'knows' links Person to Person,
    range_="http://schema.org/Person". This enables:

    - Type validation in future implementations
    - Automatic type constraints in SPARQL queries
    - Schema documentation and introspection

    Currently range_ is stored for future use but not enforced.

    Example:
        >>> class Person(Model):
        ...     iri: Annotated[str, SubjectField()]
        ...     rdf_type: Annotated[str, IRIField(RDF_TYPE, default="http://schema.org/Person")]
        ...     knows: Annotated[str | None, ObjectPropertyField(
        ...         "http://schema.org/knows",
        ...         range_="http://schema.org/Person"
        ...     )] = None
        >>>
        >>> # Filtering produces correct IRI syntax
        >>> query = session.query(Person).filter_by(
        ...     knows="http://example.org/bob"
        ... )
        >>> # Generated SPARQL: FILTER(?knows = <http://example.org/bob>)
    """

    def __init__(
        self,
        predicate: "str | PropertyPath",
        range_: str | None = None,
        required: bool = False,
        default: Any = None,
        validator: Callable[[Any], None] | list[Callable[[Any], None]] | None = None,
        **kwargs: Any,
    ):
        super().__init__(
            predicate=predicate,
            required=required,
            default=default,
            validator=validator,
            **kwargs,
        )
        self.range: str | None = range_

    @override
    def _copy_kwargs(self) -> dict[str, Any]:
        """Return range_ for copy(), merged with parent kwargs."""
        return {**super()._copy_kwargs(), "range_": self.range}

    # format_value() is inherited from IRIField


class SubjectField(IRIField):
    """
    Field that maps to the RDF subject (the ?s variable in SPARQL).

    Inherits from IRIField to automatically handle IRI formatting in SPARQL
    queries. This ensures that when filtering by subject IRI, the value is
    correctly formatted with angle brackets (``<http://...>``) rather than
    as a string literal. The inheritance also enables consistent type checking:
    ``isinstance(field, IRIField)`` correctly identifies SubjectField as an
    IRI-valued field.

    Unlike other fields, SubjectField doesn't represent a predicate relationship.
    It represents the subject IRI itself, allowing you to filter and access
    the subject IRI as a named field in your model.

    This is particularly useful for modeling property relationships (e.g., rdfs:label)
    where you need to query for ``?subject <property> ?value`` patterns and filter
    by the subject.

    Usage:
        >>> class Label(Model):
        ...     # No rdf:type field - property relationship, not a typed entity
        ...     entity_iri: Annotated[str, SubjectField()]
        ...     text: Annotated[str, LangString("rdfs:label")]
        >>>
        >>> # Filter by subject IRI - uses VALUES clause for efficiency
        >>> labels = session.query(Label).filter_by(
        ...     entity_iri="http://www.wikidata.org/entity/Q682"
        ... ).all()

    Note:
        - Each model can have at most one SubjectField
        - SubjectField does not generate a triple pattern in SPARQL
        - Values are populated from the ?s binding in query results
        - When used with filter_by(), generates a VALUES clause rather than FILTER
          for better query performance

    See Also:
        - :class:`LiteralList`, :class:`LangStringList`, :class:`IRIList` for
          collection fields that aggregate multiple values into lists
    """

    # SubjectField allows 'description' as an extra kwarg for documentation
    _ALLOWED_EXTRA_KWARGS: frozenset[str] = frozenset({"description"})

    def __init__(
        self,
        *,
        required: bool = False,
        default: Any = None,
        validator: Callable[[Any], None] | list[Callable[[Any], None]] | None = None,
        description: str | None = None,
        **kwargs: Any,
    ):
        """
        Initialize a SubjectField.

        Args:
            required: Whether this field is required (default: False)
            default: Default value if not provided (default: None)
            validator: Optional validation function(s)
            description: Human-readable description of this field
            **kwargs: Additional keyword arguments passed to parent
        """
        # SubjectField has no predicate - it IS the subject
        # Use special marker that won't conflict with real predicates
        super().__init__(
            predicate=_SUBJECT_FIELD_PREDICATE_MARKER,
            required=required,
            default=default,
            validator=validator,
            description=description,
            **kwargs,
        )
        self.is_subject_field: bool = True

    @override
    def _copy_kwargs(self) -> dict[str, Any]:
        """Return empty dict - SubjectField has no extra copy kwargs.

        Note: description is stored in extra_kwargs and handled separately.
        """
        return {}

    @override
    def copy(self) -> "SubjectField":
        """Create a deep copy of this SubjectField.

        Overrides base implementation because SubjectField's __init__
        doesn't accept a predicate parameter (it's keyword-only).
        """
        return type(self)(
            required=self.required,
            default=self.default,
            validator=self.validators,
            **self.extra_kwargs,
        )

    # format_value() is inherited from IRIField


__all__ = [
    "IRIField",
    "InverseField",
    "ObjectPropertyField",
    "SubjectField",
]
