# src/rhosocial/activerecord/base/field_proxy.py
"""
Field proxy implementation for accessing model fields via Model.proxy_name.field_name syntax.

This module provides the FieldProxy class, which allows users to access model fields
using syntax like User.c.name, and generates corresponding SQL expression objects.
Supports:
- Normal field access
- UseColumn annotated fields
- Dynamic table aliases
- Predefined table aliases
- Column aliases
- Self-join queries
"""

from typing import TYPE_CHECKING

from ..backend.expression.core import Column

if TYPE_CHECKING:
    from ..backend.dialect.base import SQLDialectBase
    from ..model import ActiveRecord



class FieldProxy:
    """
    Field proxy descriptor for accessing fields via Model.proxy_name.field_name syntax.

    This class enables the convenient dot notation for accessing model fields in queries,
    such as User.c.name or Post.c.created_at. It dynamically translates field access
    into appropriate SQL expression objects that can be used in WHERE clauses, comparisons,
    and other query operations.

    The FieldProxy supports:
    - Direct field access (e.g., User.c.id)
    - Field comparisons (e.g., User.c.age > 18)
    - Field functions (e.g., User.c.name.like('%john%'))
    - Custom column names via UseColumn annotations
    - Table aliases for self-joins and complex queries

    Usage:
        from typing import ClassVar

        class User(ActiveRecord):
            id: int
            name: str
            email: str
            # IMPORTANT: Field proxy must be declared as ClassVar to avoid being tracked by Pydantic
            c: ClassVar[FieldProxy] = FieldProxy()

        # Use in queries
        users = User.query().where(User.c.name == 'John').all()
        active_users = User.query().where(User.c.status == 'active').all()
        users_with_john = User.query().where(User.c.name.like('%John%')).all()
    """

    def __init__(self, table_alias: str = None):
        """
        Initialize field proxy with optional table alias.

        Args:
            table_alias: Optional table alias that will be applied to all columns
                        generated by this proxy. Useful for self-joins and complex
                        queries where multiple instances of the same table are involved.

        Example:
            # Without alias (standard usage)
            User.c.name  # References 'users' table

            # With alias (for self-joins)
            user_alias = FieldProxy(table_alias='u1')
            u1 = type('UserAlias', (object,), {'c': user_alias})()
            u1.c.name  # References 'u1' alias of 'users' table
        """
        self._table_alias = table_alias

    def __get__(self, instance, owner):
        """
        Descriptor method to return a field accessor for the given model class.

        This method is called when accessing the field proxy attribute on a class
        (e.g., when accessing User.c). It returns a _FieldAccessor instance that
        can be used to access individual fields.

        Args:
            instance: The instance that the attribute was accessed from (None when
                     accessed from class)
            owner: The class that owns this descriptor (e.g., User class)

        Returns:
            _FieldAccessor: An object that allows field-by-field access
        """
        # Return a dynamic field accessor
        class _FieldAccessor:
            """
            Internal class that provides field access functionality for a specific model.

            This class is instantiated for each model class that uses FieldProxy.
            It handles the translation of field names to column names and creates
            appropriate SQL expression objects.
            """
            def __init__(self, model_class: 'ActiveRecord', static_table_alias: str = None):
                """
                Initialize the field accessor for a specific model class.

                Args:
                    model_class: The ActiveRecord model class this accessor is for
                    static_table_alias: Optional table alias to use for all columns
                """
                self._model_class = model_class
                self._table_alias = static_table_alias  # Could be set during initialization

            def with_table_alias(self, alias: str):
                """
                Create a new field accessor with the specified table alias.

                This method is useful for creating aliased versions of the same table
                in self-joins or complex queries.

                Args:
                    alias: The table alias to use

                Returns:
                    _FieldAccessor: A new accessor instance with the specified alias

                Example:
                    # For self-join queries
                    managers = User.query().join(
                        User.alias('subordinates'),
                        User.c.id == User.alias('subordinates').reports_to_id
                    ).select(
                        User.c.name.as_('manager'),
                        User.alias('subordinates').name.as_('subordinate')
                    ).all()
                """
                new_accessor = _FieldAccessor(self._model_class, alias)
                return new_accessor

            def __getattr__(self, field_name: str):
                """
                Dynamically create a Column expression for the requested field.

                This method is called when accessing a specific field (e.g., User.c.name).
                It looks up the field in the model's field definitions, resolves the
                appropriate column name (handling UseColumn annotations), and creates
                a Column expression object.

                Args:
                    field_name: The name of the field to access

                Returns:
                    Column: A SQL expression object representing the field

                Raises:
                    AttributeError: If the field doesn't exist on the model

                Example:
                    # Accessing User.c.name returns a Column object that can be used in queries
                    where_clause = User.c.name == 'John'  # Creates a comparison predicate
                    where_clause = User.c.age > 18        # Creates a comparison predicate
                    where_clause = User.c.email.like('%@gmail.com')  # Creates a LIKE predicate
                """
                # Use Pydantic's model_fields to get field information
                if field_name not in self._model_class.model_fields:
                    raise AttributeError(f"Field '{field_name}' does not exist on model '{self._model_class.__name__}'")

                # Use ColumnNameMixin's method to get the correct column name
                # This properly handles UseColumn annotations, returning custom column name if UseColumn is used, otherwise field name
                column_name = self._model_class._get_column_name(field_name)

                # Use table alias (if set) as table name
                table_name = self._table_alias if self._table_alias else self._model_class.table_name()

                # Create column expression object using the real dialect
                backend = self._model_class.backend()
                dialect: 'SQLDialectBase' = backend.dialect
                return Column(dialect, column_name, table=table_name)

        return _FieldAccessor(owner, self._table_alias)