import json
from datetime import date, datetime
from typing import Any, Tuple, List, Type, TYPE_CHECKING
import re
import uuid
import logging

from atomsql.utils.dates import parse_date_string

logger = logging.getLogger(__name__)

# on_delete cascade options for foreign keys
class PROTECT:
    """Prevent deletion of the referenced object by raising an IntegrityError."""

    pass


class CASCADE:
    """Delete the referencing objects when the referenced object is deleted."""

    pass


class SET_NULL:
    """Set the foreign key field to NULL when the referenced object is deleted.
    The field must be nullable=True."""

    pass


class SET_DEFAULT:
    """Set the foreign key field to its default value when the referenced object is deleted.
    The field must have a default value."""

    pass


# Create instances for use as on_delete parameters
PROTECT = PROTECT()
CASCADE = CASCADE()
SET_NULL = SET_NULL()
SET_DEFAULT = SET_DEFAULT()

if TYPE_CHECKING:
    from .models import Model


class MultipleObjectsReturned(Exception):
    """Exception raised when multiple objects are returned for a OneToOneField."""

    pass


class DoesNotExist(Exception):
    """Exception raised when no object exists for a OneToOneField."""

    pass


EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
URL_REGEX = re.compile(
    r"^(?:http|ftp)s?://"  # http:// or https://
    r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|"  # domain...
    r"localhost|"  # localhost...
    r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"  # ...or ip
    r"(?::\d+)?"  # optional port
    r"(?:/?|[/?]\S+)$",
    re.IGNORECASE,
)


class BinaryExpression:
    def __init__(self, field: "Field", operator: str, value: Any):
        self.field = field
        self.operator = operator
        self.value = value

    def to_sql(self, placeholder_char: str = "?") -> Tuple[str, List[Any]]:
        # Use qualified table name if field has an owner (Model class)
        if self.field.owner and hasattr(self.field.owner, "_table_name"):
            table_name = self.field.owner._table_name
            sql = (
                f'"{table_name}"."{self.field.name}" {self.operator} {placeholder_char}'
            )
        else:
            sql = f'"{self.field.name}" {self.operator} {placeholder_char}'
        return sql, [self.value]

    def __repr__(self):
        return f"<BinaryExpression: {self.field.name} {self.operator} {self.value}>"


class Field:
    def __init__(
        self,
        default: Any = None,
        unique: bool = False,
        nullable: bool = True,
        choices: list = None,
    ):
        if not isinstance(unique, bool):
            raise TypeError("Option 'unique' must be a boolean")
        if not isinstance(nullable, bool):
            raise TypeError("Option 'nullable' must be a boolean")
        if choices is not None and not isinstance(choices, (list, tuple)):
            raise TypeError("Option 'choices' must be a list or tuple")
        if choices is not None and len(choices) == 0:
            raise ValueError("Option 'choices' must not be empty")

        self.default = default
        self.unique = unique
        self.nullable = nullable
        self.choices = tuple(choices) if choices is not None else None
        self.name = None
        self.owner = None

    def __set_name__(self, owner, name):
        self.name = name
        self.owner = owner

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        if value is None:
            if not self.nullable:
                raise ValueError(f"Field '{self.name}' cannot be None (nullable=False)")
            instance.__dict__[self.name] = None
            return
        self.validate_type(value)
        self.validate_choices(value)
        instance.__dict__[self.name] = value

    def validate_type(self, value):
        raise NotImplementedError

    def validate_choices(self, value):
        """Validate that value is in choices if choices are defined."""
        if self.choices is not None and value not in self.choices:
            raise ValueError(
                f"Field '{self.name}' value {value!r} is not in choices: {self.choices}"
            )

    def get_sql_type(self) -> str:
        return "TEXT"

    def get_column_name(self) -> str:
        """Return the actual column name to use in the database."""
        return self.name

    def to_sql_value(self, instance: Any) -> Any:
        return getattr(instance, self.name)

    def from_db_value(self, value: Any) -> Any:
        """Convert a value from the database to a Python object."""
        return value

    def __eq__(self, value: Any) -> BinaryExpression:
        return BinaryExpression(self, "=", value)

    def __ne__(self, value: Any) -> BinaryExpression:
        return BinaryExpression(self, "!=", value)

    def __lt__(self, value: Any) -> BinaryExpression:
        return BinaryExpression(self, "<", value)

    def __le__(self, value: Any) -> BinaryExpression:
        return BinaryExpression(self, "<=", value)

    def __gt__(self, value: Any) -> BinaryExpression:
        return BinaryExpression(self, ">", value)

    def __ge__(self, value: Any) -> BinaryExpression:
        return BinaryExpression(self, ">=", value)


class BooleanField(Field):
    def validate_type(self, value):
        if value is None and self.nullable:
            return
        if not isinstance(value, bool):
            raise ValueError(f"Field '{self.name}' expected a bool, got {type(value)}.")

    def from_db_value(self, value):
        """
        Convert database representation to a Python boolean.
        Handles Sqlite integer representation (0/1) and PostgreSQL boolean (native 't'/'f').
        """
        if value is None:
            return None
        return bool(value)


class IntegerField(Field):
    def validate_type(self, value):
        if value is None and self.nullable:
            return
        if not isinstance(value, int):
            raise ValueError(f"Field '{self.name}' expected an int, got {type(value)}.")


class TimeStampField(IntegerField):
    def validate_type(self, value):
        if value is None and self.nullable:
            return
        if not isinstance(value, int):
            raise ValueError(
                f"Field '{self.name}' expected an int timestamp, got {type(value)}."
            )


class StringField(Field):
    def validate_type(self, value):
        if value is None and self.nullable:
            return
        if not isinstance(value, str):
            raise ValueError(f"Field '{self.name}' expected a str, got {type(value)}.")

    def __set__(self, instance, value):
        self.validate_type(value)
        return super().__set__(instance, value)


class CharField(StringField):
    """
    A field for shorter strings with a maximum length
    Maps to VARCHAR(max_length) in SQL databases.
    """

    def __init__(
        self,
        max_length: int,
        default: Any = None,
        unique=False,
        nullable=True,
        choices: list = None,
    ):
        super().__init__(
            default=default, unique=unique, nullable=nullable, choices=choices
        )
        if not isinstance(max_length, int) or max_length <= 0:
            raise ValueError("CharField 'max_length' must be a positive integer.")
        self.max_length = max_length

    def validate_type(self, value):
        return super().validate_type(value)


class TextField(StringField):
    """
    A field for longer text strings.
    Maps to TEXT in SQL databases.
    """

    pass


class JSONField(Field):
    def validate_type(self, value):
        if value is None and self.nullable:
            return
        try:
            json.dumps(value)
        except (TypeError, ValueError):
            raise ValueError(f"Field '{self.name}' expected a JSON-serializable value.")

    def to_sql_value(self, instance: Any) -> Any:
        """Convert Python object to JSON string for database storage."""
        value = getattr(instance, self.name)
        if value is None:
            return None
        return json.dumps(value)

    def from_db_value(self, value: Any) -> Any:
        if value is None:
            return None
        if isinstance(value, str):
            try:
                return json.loads(value)
            except json.JSONDecodeError:
                raise ValueError(
                    f"Field '{self.name}' could not parse JSON from database: {value}"
                )
        return value


class EmailField(StringField):
    def validate_type(self, value):
        super().validate_type(value)
        if value is None:
            return
        if not EMAIL_REGEX.match(value):
            raise ValueError(
                f"Field '{self.name}' expected a valid email address, got: {value}"
            )


class URLField(StringField):
    def validate_type(self, value):
        super().validate_type(value)
        if value is None:
            return
        if not URL_REGEX.match(value):
            raise ValueError(f"Field '{self.name}' expected a valid URL, got: {value}")


class FloatField(Field):
    def validate_type(self, value):
        if value is None and self.nullable:
            return
        if not isinstance(value, float):
            raise ValueError(
                f"Field '{self.name}' expected a float, got {type(value)}."
            )

    def __set__(self, instance, value):
        self.validate_type(value)
        return super().__set__(instance, value)


class DateField(Field):
    def validate_type(self, value):
        if value is None and self.nullable:
            return value
        # Only accept date objects (not datetime)
        if not isinstance(value, date) or isinstance(value, datetime):
            raise ValueError(f"Field '{self.name}' expected a date, got {type(value)}.")
        return value

    def from_db_value(self, value: Any) -> Any:
        """Convert database string to date object."""
        if value is None:
            return None
        if isinstance(value, date):
            return value
        if isinstance(value, str):
            try:
                parsed = parse_date_string(value)
                if parsed is None:
                    raise ValueError
                return parsed
            except (ValueError, AttributeError):
                raise ValueError(
                    f"Field '{self.name}' could not parse date from database: {value}"
                )
        return value


class DateTimeField(Field):
    def validate_type(self, value):
        if value is None and self.nullable:
            return value
        # Only accept datetime objects (not plain date)
        if not isinstance(value, datetime):
            raise ValueError(
                f"Field '{self.name}' expected a datetime, got {type(value)}."
            )
        return value

    def from_db_value(self, value: Any) -> Any:
        """Convert database string to datetime object."""
        if value is None:
            return None
        if isinstance(value, datetime):
            return value
        if isinstance(value, str):
            try:
                parsed = parse_date_string(value)
                if parsed is None:
                    raise ValueError
                return parsed
            except (ValueError, AttributeError):
                raise ValueError(
                    f"Field '{self.name}' could not parse datetime from database: {value}"
                )
        return value


class DateTimeTZField(DateTimeField):
    """
    Strictly accepts timezone-aware Python datetime objects.
    """

    def validate_type(self, value):
        super().validate_type(value)

        if value is None:
            return

        if value.tzinfo is None or value.tzinfo.utcoffset(value) is None:
            raise ValueError(
                f"Field '{self.name}' expected a timezone-aware datetime, got naive datetime."
            )
        return value


class UUIDField(Field):
    """
    A field for storing UUIDs.
    Supports auto-generation using uuid.uuid4().
    """

    def __init__(
        self, default: Any = None, unique=False, nullable=True, auto_add=False
    ):
        if auto_add and default is None:
            default = uuid.uuid4
        super().__init__(default=default, unique=unique, nullable=nullable)
        self.auto_add = auto_add

    def validate_type(self, value):
        if value is None and self.nullable:
            return value
        if not isinstance(value, (uuid.UUID, str)):
            raise ValueError(f"Field '{self.name}' expected a UUID, got {type(value)}.")
        if isinstance(value, str):
            try:
                uuid.UUID(value)
            except ValueError:
                raise ValueError(
                    f"Field '{self.name}' expected a valid UUID, got: {value}"
                )

    def to_sql_value(self, instance: Any) -> Any:
        return (
            str(getattr(instance, self.name)) if getattr(instance, self.name) else None
        )

    def from_db_value(self, value: Any) -> Any:
        if value is None:
            return None
        if isinstance(value, uuid.UUID):
            return value
        try:
            return uuid.UUID(value)
        except ValueError:
            raise ValueError(
                f"Field '{self.name}' could not parse UUID from database: {value}"
            )


class ReverseRelation:
    def __init__(self, related_model: Type["Model"], field_name: str):
        self.related_model = related_model
        self.field_name = field_name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        fk_field = self.related_model._fields[self.field_name]
        column_name = fk_field.get_column_name()

        return self.related_model.filter(**{column_name: instance.id})


class OneToOneReverseRelation:
    """Handles reverse lookups for OneToOneField relationships."""

    def __init__(self, related_model: Type["Model"], field_name: str):
        self.related_model = related_model
        self.field_name = field_name

    def __get__(self, instance, owner):
        if instance is None:
            return self

        # Check cache first using consistent cache key format
        cache_name = f"_{self.related_model._table_name.lower()}_reverse_cache"
        cached = instance.__dict__.get(cache_name)
        if cached is not None:
            return cached

        fk_field = self.related_model._fields[self.field_name]
        column_name = fk_field.get_column_name()

        # Query for related objects
        queryset = self.related_model.filter(**{column_name: instance.id})
        results = list(queryset)

        if len(results) == 0:
            raise DoesNotExist(
                f"{self.related_model.__name__} matching query does not exist."
            )
        elif len(results) > 1:
            raise MultipleObjectsReturned(
                f"get() returned more than one {self.related_model.__name__} -- "
                f"it returned {len(results)}! Lookup parameters were {{{column_name}: {instance.id}}}"
            )

        result = results[0]
        instance.__dict__[cache_name] = result
        return result
        instance.__dict__[cache_name] = result
        return result


class ForeignKey(Field):
    def __init__(
        self,
        to: Type["Model"],
        nullable: bool = False,
        related_name: str = None,
        on_delete=CASCADE,
        **kwargs,
    ):
        super().__init__(nullable=nullable, **kwargs)
        self.to = to
        self.related_name = related_name
        self.on_delete = on_delete

        # Validate on_delete option compatibility
        if on_delete is SET_NULL and not nullable:
            raise ValueError("Cannot use SET_NULL on_delete with nullable=False")

    def __set_name__(self, owner, name):
        self.name = name
        if self.related_name:
            back_ref_name = self.related_name
        else:
            back_ref_name = f"{owner._table_name}_set"

        if not hasattr(self.to, back_ref_name):
            setattr(self.to, back_ref_name, ReverseRelation(owner, name))

    def get_sql_type(self) -> str:
        return "INTEGER"

    def get_column_name(self) -> str:
        """ForeignKey columns have _id suffix."""
        return f"{self.name}_id"

    def __set__(self, instance, value):
        if value is None:
            if not self.nullable:
                raise ValueError(f"Field '{self.name}' cannot be None (nullable=False)")
            instance.__dict__[f"{self.name}_id"] = None
            instance.__dict__[f"_{self.name}_cache"] = None
            return
        if isinstance(value, self.to):
            instance.__dict__[f"_{self.name}_cache"] = value
            if hasattr(value, "id"):
                instance.__dict__[f"{self.name}_id"] = getattr(value, "id")
        elif isinstance(value, int):
            instance.__dict__[f"{self.name}_id"] = value
            instance.__dict__[f"_{self.name}_cache"] = None
        else:
            raise ValueError(
                f"Expected {self.to.__name__} instance or int ID, got {type(value)}."
            )

    def __get__(self, instance, owner):
        if instance is None:
            return self
        cached = instance.__dict__.get(f"_{self.name}_cache")
        if cached is not None:
            return cached
        fk_id = instance.__dict__.get(f"{self.name}_id")
        if fk_id is not None:
            related_instance = self.to.get(fk_id)
            instance.__dict__[f"_{self.name}_cache"] = related_instance
            return related_instance
        return None

    def to_sql_value(self, instance: Any) -> Any:
        return instance.__dict__.get(f"{self.name}_id")


class OneToOneField(ForeignKey):
    """
    A one-to-one relationship field. This is essentially a ForeignKey with unique=True.
    The reverse relationship returns a single instance, not a QuerySet.

    Raises MultipleObjectsReturned if multiple related objects exist.
    Raises DoesNotExist if no related object exists on reverse lookup.
    """

    def __init__(
        self,
        to: Type["Model"],
        nullable: bool = False,
        related_name: str = None,
        on_delete=CASCADE,
        **kwargs,
    ):
        # OneToOneField is essentially a ForeignKey with unique=True
        super().__init__(
            to=to,
            nullable=nullable,
            related_name=related_name,
            on_delete=on_delete,
            **kwargs,
        )
        # Force unique constraint
        self.unique = True

    def __set_name__(self, owner, name):
        self.name = name
        if self.related_name:
            back_ref_name = self.related_name
        else:
            back_ref_name = owner._table_name.lower()

        # Set up reverse relationship using OneToOneReverseRelation instead of ReverseRelation
        if not hasattr(self.to, back_ref_name):
            setattr(self.to, back_ref_name, OneToOneReverseRelation(owner, name))

    def __get__(self, instance, owner):
        if instance is None:
            return self

        # Check cache first
        cached = instance.__dict__.get(f"_{self.name}_cache")
        if cached is not None:
            return cached

        fk_id = instance.__dict__.get(f"{self.name}_id")
        if fk_id is not None:
            related_instance = self.to.get(fk_id)
            instance.__dict__[f"_{self.name}_cache"] = related_instance
            return related_instance
        return None

    def __set__(self, instance, value):
        # Get the old value to clear its reverse cache
        old_fk_id = instance.__dict__.get(f"{self.name}_id")

        # Call parent's __set__ method to handle the basic assignment
        super().__set__(instance, value)

        # Get the new value
        new_fk_id = instance.__dict__.get(f"{self.name}_id")

        # Clear reverse relationship caches if the relationship changed
        if old_fk_id != new_fk_id:
            self._clear_reverse_caches(old_fk_id, new_fk_id, instance.__class__)

    def _clear_reverse_caches(self, old_fk_id, new_fk_id, source_model_class):
        """Clear cached reverse relationships that might be stale."""
        cache_name = f"_{source_model_class._table_name.lower()}_reverse_cache"

        # Clear cache on old related object if it exists
        if old_fk_id is not None:
            try:
                old_related_instance = self.to.get(old_fk_id)
                if cache_name in old_related_instance.__dict__:
                    del old_related_instance.__dict__[cache_name]
            except Exception:  # noqa: S110
                pass  # Instance might not exist or be accessible

        # Clear cache on new related object if it exists
        if new_fk_id is not None:
            try:
                new_related_instance = self.to.get(new_fk_id)
                if cache_name in new_related_instance.__dict__:
                    del new_related_instance.__dict__[cache_name]
            except Exception:  # noqa: S110
                pass  # Instance might not exist or be accessible


class ManyToManyManager:
    """Manager for handling many-to-many relationships."""

    def __init__(self, field: "ManyToManyField", instance: "Model"):
        self.field = field
        self.instance = instance
        self.through_table = field.get_through_table_name()
        self.source_field = field.get_source_field_name()
        self.target_field = field.get_target_field_name()

    def all(self):
        """Return all related objects."""
        if self.instance.id is None:
            return self.field.to.objects.none()

        # Join through the through table
        sql = f"""
        SELECT t.* FROM "{self.field.to._table_name}" t
        INNER JOIN "{self.through_table}" tt ON t.id = tt.{self.target_field}
        WHERE tt.{self.source_field} = ?
        """

        db_interface = self.instance._db or self.field.to._db
        cursor = db_interface.backend.execute(sql, [self.instance.id])
        results = []

        for row in cursor.fetchall():
            # Convert row to instance
            instance_data = {}
            if cursor.description:
                for i, column_desc in enumerate(cursor.description):
                    # Handle different cursor description formats
                    if isinstance(column_desc, (list, tuple)):
                        column_name = column_desc[0]
                    else:
                        column_name = column_desc

                    # Find the field name from column name
                    field_name = None
                    for fname, field in self.field.to._fields.items():
                        if field.get_column_name() == column_name:
                            field_name = fname
                            break

                    if field_name:
                        instance_data[field_name] = self.field.to._fields[
                            field_name
                        ].from_db_value(row[i])

            instance = self.field.to(**instance_data)
            results.append(instance)

        return results

    def add(self, *instances):
        """Add instances to the relationship."""
        if self.instance.id is None:
            raise ValueError("Instance must be saved before adding relationships")

        db_interface = self.instance._db or self.field.to._db
        if db_interface is None:
            raise RuntimeError("No database connection available")

        for instance in instances:
            if not isinstance(instance, self.field.to):
                raise TypeError(f"Expected {self.field.to.__name__} instance")

            if instance.id is None:
                raise ValueError(
                    "Related instance must be saved before adding to relationship"
                )

            # Check if relationship already exists
            check_sql = f"""
            SELECT 1 FROM "{self.through_table}"
            WHERE {self.source_field} = ? AND {self.target_field} = ?
            """
            cursor = db_interface.backend.execute(
                check_sql, [self.instance.id, instance.id]
            )
            if cursor.fetchone():
                continue  # Relationship already exists

            # Insert the relationship
            insert_sql = f"""
            INSERT INTO "{self.through_table}" ({self.source_field}, {self.target_field})
            VALUES (?, ?)
            """
            db_interface.backend.execute(insert_sql, [self.instance.id, instance.id])

        db_interface.backend.commit()

    def remove(self, *instances):
        """Remove instances from the relationship."""
        if self.instance.id is None:
            return  # Nothing to remove

        db_interface = self.instance._db or self.field.to._db
        if db_interface is None:
            raise RuntimeError("No database connection available")

        instance_ids = []
        for instance in instances:
            if not isinstance(instance, self.field.to):
                raise TypeError(f"Expected {self.field.to.__name__} instance")
            if instance.id is not None:
                instance_ids.append(instance.id)

        if instance_ids:
            placeholders = ", ".join(["?"] * len(instance_ids))
            sql = f"""
            DELETE FROM "{self.through_table}"
            WHERE {self.source_field} = ? AND {self.target_field} IN ({placeholders})
            """
            params = [self.instance.id] + instance_ids
            db_interface.backend.execute(sql, params)
            db_interface.backend.commit()

    def clear(self):
        """Remove all relationships."""
        if self.instance.id is None:
            return

        db_interface = self.instance._db or self.field.to._db
        if db_interface is None:
            raise RuntimeError("No database connection available")

        sql = f'DELETE FROM "{self.through_table}" WHERE {self.source_field} = ?'
        db_interface.backend.execute(sql, [self.instance.id])
        db_interface.backend.commit()

    def set(self, instances):
        """Set the relationship to exactly these instances."""
        if not hasattr(instances, "__iter__"):
            instances = [instances]

        self.clear()
        if instances:
            self.add(*instances)

    def count(self):
        """Return the count of related objects."""
        if self.instance.id is None:
            return 0

        db_interface = self.instance._db or self.field.to._db
        if db_interface is None:
            raise RuntimeError("No database connection available")

        sql = (
            f'SELECT COUNT(*) FROM "{self.through_table}" WHERE {self.source_field} = ?'
        )
        cursor = db_interface.backend.execute(sql, [self.instance.id])
        result = cursor.fetchone()
        return result[0] if result else 0

    def filter(self, **kwargs):
        """Filter related objects."""
        if self.instance.id is None:
            return self.field.to.objects.none()

        # Build filter conditions for the target model
        conditions = []
        params = [self.instance.id]

        for field_name, value in kwargs.items():
            if field_name not in self.field.to._fields:
                raise ValueError(
                    f"Field '{field_name}' does not exist on {self.field.to.__name__}"
                )

            field = self.field.to._fields[field_name]
            column_name = field.get_column_name()
            conditions.append(f't."{column_name}" = ?')
            params.append(value)

        where_clause = " AND ".join(conditions)
        if where_clause:
            where_clause = " AND " + where_clause

        sql = f"""
        SELECT t.* FROM "{self.field.to._table_name}" t
        INNER JOIN "{self.through_table}" tt ON t.id = tt.{self.target_field}
        WHERE tt.{self.source_field} = ?{where_clause}
        """

        db_interface = self.instance._db or self.field.to._db
        cursor = db_interface.backend.execute(sql, params)
        results = []

        for row in cursor.fetchall():
            instance_data = {}
            if cursor.description:
                for i, column_desc in enumerate(cursor.description):
                    # Handle different cursor description formats
                    if isinstance(column_desc, (list, tuple)):
                        column_name = column_desc[0]
                    else:
                        column_name = column_desc

                    # Find the field name from column name
                    field_name = None
                    for fname, field in self.field.to._fields.items():
                        if field.get_column_name() == column_name:
                            field_name = fname
                            break

                    if field_name:
                        instance_data[field_name] = self.field.to._fields[
                            field_name
                        ].from_db_value(row[i])

            instance = self.field.to(**instance_data)
            results.append(instance)

        return results


class ManyToManyField(Field):
    """
    A field for representing many-to-many relationships between models.
    Creates a junction table automatically or uses a custom through model.
    """

    def __init__(
        self, to: Type["Model"], through: Type["Model"] = None, related_name: str = None
    ):
        # ManyToManyField is never nullable in the traditional sense
        super().__init__(nullable=True)
        self.to = to
        self.through = through
        self.related_name = related_name
        self._through_table_created = False

    def __set_name__(self, owner, name):
        self.name = name
        self.owner = owner

        # Set up reverse relationship
        if self.related_name:
            back_ref_name = self.related_name
        else:
            back_ref_name = f"{owner._table_name}_set"

        # Create reverse ManyToManyField if it doesn't exist
        if not hasattr(self.to, back_ref_name):
            reverse_field = ManyToManyField(
                owner, through=self.through, related_name=name
            )
            reverse_field.name = back_ref_name
            reverse_field.owner = self.to
            setattr(self.to, back_ref_name, reverse_field)

    def get_through_table_name(self) -> str:
        """Get the name of the through/junction table."""
        if self.through:
            return self.through._table_name

        # Generate automatic through table name
        table_names = sorted([self.owner._table_name, self.to._table_name])
        return f"{table_names[0]}_{table_names[1]}"

    def get_source_field_name(self) -> str:
        """Get the field name for the source model in the through table."""
        return f"{self.owner._table_name}_id"

    def get_target_field_name(self) -> str:
        """Get the field name for the target model in the through table."""
        return f"{self.to._table_name}_id"

    def create_through_table(self, db_interface):
        """Create the through/junction table if it doesn't exist."""
        if self._through_table_created or self.through:
            return

        through_table = self.get_through_table_name()
        source_field = self.get_source_field_name()
        target_field = self.get_target_field_name()

        # Create table SQL
        sql = f"""
        CREATE TABLE IF NOT EXISTS "{through_table}" (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            {source_field} INTEGER NOT NULL,
            {target_field} INTEGER NOT NULL,
            FOREIGN KEY ({source_field}) REFERENCES "{self.owner._table_name}" (id) ON DELETE CASCADE,
            FOREIGN KEY ({target_field}) REFERENCES "{self.to._table_name}" (id) ON DELETE CASCADE,
            UNIQUE({source_field}, {target_field})
        )
        """

        try:
            db_interface.backend.execute(sql)
            db_interface.backend.commit()
            self._through_table_created = True

            # Also mark the reverse field's through table as created to avoid duplicates
            if hasattr(self.to, f"{self.owner._table_name}_set"):
                reverse_field = getattr(self.to, f"{self.owner._table_name}_set")
                if hasattr(reverse_field, "_through_table_created"):
                    reverse_field._through_table_created = True

        except Exception as e:
            # If the table already exists, that's fine
            if "already exists" not in str(e).lower():
                logger.warning(f"Error creating through table {through_table}: {e}")
                raise

    def get_sql_type(self) -> str:
        """ManyToManyField doesn't have a direct SQL type."""
        return None

    def get_column_name(self) -> str:
        """ManyToManyField doesn't have a direct column."""
        return None

    def to_sql_value(self, instance: Any) -> Any:
        """ManyToManyField doesn't store direct values."""
        return None

    def __get__(self, instance, owner):
        if instance is None:
            return self

        # Return a manager for this relationship
        cache_name = f"_{self.name}_manager"
        manager = instance.__dict__.get(cache_name)

        if manager is None:
            manager = ManyToManyManager(self, instance)
            instance.__dict__[cache_name] = manager

            # Ensure through table exists
            db_interface = instance._db or self.to._db
            if db_interface:
                self.create_through_table(db_interface)

        return manager

    def __set__(self, instance, value):
        """Setting a ManyToManyField replaces all relationships."""
        manager = self.__get__(instance, type(instance))
        if value is None:
            manager.clear()
        else:
            if hasattr(value, "__iter__"):
                manager.set(value)
            else:
                manager.set([value])
