"""Self-contained ORM layer for Soi Gia.

This module keeps the ORM core in one file on purpose:
- declarative fields and model metadata
- registry and environment objects
- query layer
- memory, pandas, and sqlite backends
- session and unit-of-work helpers
"""

from __future__ import annotations

import copy
import inspect
import json
import sqlite3
from collections import OrderedDict
from dataclasses import dataclass, field
from datetime import date, datetime
from pathlib import Path
from types import SimpleNamespace
from typing import Any, Callable, Iterable, Iterator

try:
    import pandas as pd
except Exception:  # pragma: no cover - pandas is expected in this repo
    pd = None  # type: ignore[assignment]

__all__ = [
    "Expression",
    "Condition",
    "Field",
    "String",
    "Integer",
    "Float",
    "Boolean",
    "Date",
    "DateTime",
    "Text",
    "ForeignKey",
    "OneToMany",
    "ManyToMany",
    "ModelOptions",
    "Registry",
    "BackendAdapter",
    "MemoryBackend",
    "PandasBackend",
    "SQLiteBackend",
    "Repository",
    "RecordSet",
    "QuerySet",
    "Manager",
    "Env",
    "Session",
    "UnitOfWork",
    "Model",
    "fields",
    "default_registry",
    "default_env",
]


def _call_maybe(func: Callable[..., Any], instance: Any = None) -> Any:
    try:
        return func()
    except TypeError:
        if instance is None:
            return func(instance)
        return func(instance)


def _normalize_related_value(value: Any) -> Any:
    if value is None:
        return None
    if hasattr(value, "id"):
        return getattr(value, "id")
    if isinstance(value, RecordSet):
        return value.ids
    return value


def _json_default(value: Any) -> Any:
    if isinstance(value, (date, datetime)):
        return value.isoformat()
    if isinstance(value, Path):
        return str(value)
    if hasattr(value, "id"):
        return getattr(value, "id")
    if isinstance(value, set):
        return list(value)
    raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")


LOOKUPS = {
    "eq",
    "ne",
    "gt",
    "gte",
    "lt",
    "lte",
    "in",
    "contains",
    "icontains",
    "startswith",
    "istartswith",
    "endswith",
    "iendswith",
    "isnull",
}

LOOKUP_ALIASES = {
    "=": "eq",
    "==": "eq",
    "!=": "ne",
    "<>": "ne",
}


class Expression:
    def evaluate(self, row: dict[str, Any], model_cls: type["Model"], env: "Env") -> bool:
        raise NotImplementedError


@dataclass(slots=True)
class Condition(Expression):
    field_name: str
    lookup: str = "eq"
    value: Any = None
    negated: bool = False

    def evaluate(self, row: dict[str, Any], model_cls: type["Model"], env: "Env") -> bool:
        field = model_cls._meta.fields.get(self.field_name)
        actual = row.get(self.field_name)
        expected = _normalize_related_value(self.value)

        if field is not None:
            actual = field.from_storage(actual, None)
            if isinstance(field, ForeignKey):
                actual = _normalize_related_value(actual)
        else:
            actual = _normalize_related_value(actual)

        result = self._compare(actual, expected)
        return not result if self.negated else result

    def _compare(self, actual: Any, expected: Any) -> bool:
        lookup = LOOKUP_ALIASES.get(self.lookup, self.lookup)
        if lookup == "eq":
            return actual == expected
        if lookup == "ne":
            return actual != expected
        if lookup == "gt":
            return actual is not None and expected is not None and actual > expected
        if lookup == "gte":
            return actual is not None and expected is not None and actual >= expected
        if lookup == "lt":
            return actual is not None and expected is not None and actual < expected
        if lookup == "lte":
            return actual is not None and expected is not None and actual <= expected
        if lookup == "in":
            return actual in (expected or [])
        if lookup == "contains":
            return actual is not None and expected is not None and expected in actual
        if lookup == "icontains":
            return actual is not None and expected is not None and str(expected).lower() in str(actual).lower()
        if lookup == "startswith":
            return actual is not None and expected is not None and str(actual).startswith(str(expected))
        if lookup == "istartswith":
            return actual is not None and expected is not None and str(actual).lower().startswith(str(expected).lower())
        if lookup == "endswith":
            return actual is not None and expected is not None and str(actual).endswith(str(expected))
        if lookup == "iendswith":
            return actual is not None and expected is not None and str(actual).lower().endswith(str(expected).lower())
        if lookup == "isnull":
            return (actual is None) == bool(expected)
        raise ValueError(f"unsupported lookup: {lookup}")


class Field:
    python_type: type[Any] = Any

    def __init__(
        self,
        default: Any = None,
        *,
        required: bool = False,
        nullable: bool = True,
        compute: str | Callable[..., Any] | None = None,
        store: bool = True,
        read_only: bool = False,
        help_text: str | None = None,
    ) -> None:
        self.default = default
        self.required = required
        self.nullable = nullable
        self.compute = compute
        self.store = store
        self.read_only = read_only
        self.help_text = help_text
        self.name = ""
        self.model: type["Model"] | None = None

    def clone(self) -> "Field":
        return copy.copy(self)

    def __set_name__(self, owner: type["Model"], name: str) -> None:
        self.name = name
        self.model = owner

    def get_default(self, instance: "Model" | None = None) -> Any:
        if callable(self.default):
            return _call_maybe(self.default, instance)
        return copy.deepcopy(self.default)

    def validate(self, value: Any) -> Any:
        if value is None:
            if self.required and not self.nullable:
                raise ValueError(f"field {self.name!r} cannot be null")
            return None
        return value

    def to_storage(self, value: Any) -> Any:
        return value

    def from_storage(self, value: Any, instance: "Model" | None = None) -> Any:
        return value

    def compute_value(self, instance: "Model") -> Any:
        if self.compute is None:
            return None
        target = self.compute
        if isinstance(target, str):
            target = getattr(instance, target)
        return _call_maybe(target, instance)

    def __get__(self, instance: "Model" | None, owner: type["Model"]) -> Any:
        if instance is None:
            return self
        if self.compute is not None:
            value = self.compute_value(instance)
            if self.store:
                instance._values[self.name] = self.to_storage(value)
            return value
        if self.name in instance._values:
            value = instance._values[self.name]
            return self.from_storage(value, instance)
        return self.get_default(instance)

    def __set__(self, instance: "Model", value: Any) -> None:
        if self.read_only:
            raise AttributeError(f"field {self.name!r} is read-only")
        validated = self.validate(value)
        instance._values[self.name] = self.to_storage(validated)


class String(Field):
    python_type = str

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return None
        return str(value)


class Text(String):
    pass


class Integer(Field):
    python_type = int

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return None
        return int(value)


class Float(Field):
    python_type = float

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return None
        return float(value)


class Boolean(Field):
    python_type = bool

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return None
        if isinstance(value, str):
            return value.strip().lower() in {"1", "true", "yes", "y", "on"}
        return bool(value)


class Date(Field):
    python_type = date

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return None
        if isinstance(value, date) and not isinstance(value, datetime):
            return value
        if isinstance(value, str):
            return date.fromisoformat(value)
        raise TypeError(f"field {self.name!r} expects a date")

    def to_storage(self, value: Any) -> Any:
        return value.isoformat() if isinstance(value, date) else value

    def from_storage(self, value: Any, instance: "Model" | None = None) -> Any:
        if value is None or isinstance(value, date):
            return value
        return date.fromisoformat(str(value))


class DateTime(Field):
    python_type = datetime

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return None
        if isinstance(value, datetime):
            return value
        if isinstance(value, str):
            return datetime.fromisoformat(value)
        raise TypeError(f"field {self.name!r} expects a datetime")

    def to_storage(self, value: Any) -> Any:
        return value.isoformat() if isinstance(value, datetime) else value

    def from_storage(self, value: Any, instance: "Model" | None = None) -> Any:
        if value is None or isinstance(value, datetime):
            return value
        return datetime.fromisoformat(str(value))


class ForeignKey(Field):
    def __init__(self, comodel_name: str, default: Any = None, **kwargs: Any) -> None:
        super().__init__(default=default, **kwargs)
        self.comodel_name = comodel_name

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return None
        return _normalize_related_value(value)

    def __get__(self, instance: "Model" | None, owner: type["Model"]) -> Any:
        if instance is None:
            return self
        raw = instance._values.get(self.name)
        if raw is None:
            return self.get_default(instance)
        env = instance._env or default_env
        if env is None:
            return raw
        return env[self.comodel_name].browse(raw)


class OneToMany(Field):
    def __init__(self, comodel_name: str, inverse_name: str, **kwargs: Any) -> None:
        super().__init__(default=list, store=False, read_only=True, **kwargs)
        self.comodel_name = comodel_name
        self.inverse_name = inverse_name

    def __get__(self, instance: "Model" | None, owner: type["Model"]) -> Any:
        if instance is None:
            return self
        env = instance._env or default_env
        if env is None or instance.id is None:
            return RecordSet(owner=None, records=[])
        return env[self.comodel_name].search([(self.inverse_name, "=", instance.id)])

    def __set__(self, instance: "Model", value: Any) -> None:  # pragma: no cover - read only relation
        raise AttributeError(f"field {self.name!r} is read-only")


class ManyToMany(Field):
    def __init__(self, comodel_name: str, default: Any = None, **kwargs: Any) -> None:
        super().__init__(default=default or list, **kwargs)
        self.comodel_name = comodel_name

    def validate(self, value: Any) -> Any:
        value = super().validate(value)
        if value is None:
            return []
        if isinstance(value, RecordSet):
            return value.ids
        if isinstance(value, (list, tuple, set)):
            return [_normalize_related_value(item) for item in value]
        return [_normalize_related_value(value)]

    def __get__(self, instance: "Model" | None, owner: type["Model"]) -> Any:
        if instance is None:
            return self
        raw = instance._values.get(self.name)
        if raw is None:
            return self.get_default(instance) or []
        env = instance._env or default_env
        if env is None:
            return raw
        return env[self.comodel_name].browse(raw)


@dataclass(slots=True)
class ModelOptions:
    model_name: str
    table_name: str
    abstract: bool = False
    fields: OrderedDict[str, Field] = field(default_factory=OrderedDict)
    field_order: list[str] = field(default_factory=list)


class Registry:
    def __init__(self) -> None:
        self.models: dict[str, type["Model"]] = {}
        self.env: Env | None = None

    def bind_env(self, env: "Env") -> None:
        self.env = env
        for model in self.models.values():
            model._env = env
            env.backend.ensure_model(model)

    def register(self, model_cls: type["Model"]) -> type["Model"]:
        model_name = model_cls._meta.model_name
        if not model_name:
            raise ValueError(f"model {model_cls.__name__} must define _name")
        self.models[model_name] = model_cls
        model_cls._registry = self
        model_cls._env = self.env
        if self.env is not None:
            self.env.backend.ensure_model(model_cls)
        return model_cls

    def extend(self, extension_cls: type["Model"]) -> type["Model"]:
        inherit = extension_cls._inherit
        target_names = [inherit] if isinstance(inherit, str) else list(inherit or [])
        if not target_names:
            raise ValueError(f"extension {extension_cls.__name__} must define _inherit")
        targets: list[type["Model"]] = []
        for target_name in target_names:
            target = self.models.get(target_name)
            if target is None:
                raise KeyError(f"unknown model to inherit: {target_name}")
            targets.append(target)
        for target in targets:
            self._merge_extension(target, extension_cls)
        return targets[0]

    def _merge_extension(self, target: type["Model"], extension_cls: type["Model"]) -> None:
        for name, attr in extension_cls.__dict__.items():
            if name.startswith("_"):
                continue
            if isinstance(attr, Field):
                cloned = attr.clone()
                setattr(target, name, cloned)
                cloned.__set_name__(target, name)
                target._meta.fields[name] = cloned
                if name not in target._meta.field_order:
                    target._meta.field_order.append(name)
                continue
            if callable(attr):
                setattr(target, name, attr)


default_registry = Registry()


class BackendAdapter:
    name = "base"

    def ensure_model(self, model_cls: type["Model"]) -> None:
        return None

    def insert(self, model_cls: type["Model"], row: dict[str, Any]) -> dict[str, Any]:
        raise NotImplementedError

    def update(self, model_cls: type["Model"], record_id: int, row: dict[str, Any]) -> dict[str, Any]:
        raise NotImplementedError

    def delete(self, model_cls: type["Model"], record_id: int) -> None:
        raise NotImplementedError

    def get(self, model_cls: type["Model"], record_id: int) -> dict[str, Any] | None:
        raise NotImplementedError

    def all(self, model_cls: type["Model"]) -> list[dict[str, Any]]:
        raise NotImplementedError

    def search(self, model_cls: type["Model"], predicate: Callable[[dict[str, Any]], bool]) -> list[dict[str, Any]]:
        return [row for row in self.all(model_cls) if predicate(row)]


class MemoryBackend(BackendAdapter):
    name = "memory"

    def __init__(self) -> None:
        self._tables: dict[str, dict[int, dict[str, Any]]] = {}
        self._sequences: dict[str, int] = {}

    def ensure_model(self, model_cls: type["Model"]) -> None:
        self._tables.setdefault(model_cls._meta.model_name, {})
        self._sequences.setdefault(model_cls._meta.model_name, 0)

    def _next_id(self, model_cls: type["Model"]) -> int:
        name = model_cls._meta.model_name
        self._sequences[name] = self._sequences.get(name, 0) + 1
        return self._sequences[name]

    def insert(self, model_cls: type["Model"], row: dict[str, Any]) -> dict[str, Any]:
        self.ensure_model(model_cls)
        if row.get("id") is None:
            row["id"] = self._next_id(model_cls)
        self._tables[model_cls._meta.model_name][int(row["id"])] = copy.deepcopy(row)
        return copy.deepcopy(row)

    def update(self, model_cls: type["Model"], record_id: int, row: dict[str, Any]) -> dict[str, Any]:
        self.ensure_model(model_cls)
        current = self._tables[model_cls._meta.model_name].get(int(record_id))
        if current is None:
            raise KeyError(f"record {record_id} not found")
        current.update(copy.deepcopy(row))
        current["id"] = int(record_id)
        return copy.deepcopy(current)

    def delete(self, model_cls: type["Model"], record_id: int) -> None:
        self.ensure_model(model_cls)
        self._tables[model_cls._meta.model_name].pop(int(record_id), None)

    def get(self, model_cls: type["Model"], record_id: int) -> dict[str, Any] | None:
        self.ensure_model(model_cls)
        row = self._tables[model_cls._meta.model_name].get(int(record_id))
        return copy.deepcopy(row) if row is not None else None

    def all(self, model_cls: type["Model"]) -> list[dict[str, Any]]:
        self.ensure_model(model_cls)
        rows = list(self._tables[model_cls._meta.model_name].values())
        return [copy.deepcopy(row) for row in sorted(rows, key=lambda item: item["id"])]


class PandasBackend(BackendAdapter):
    name = "pandas"

    def __init__(self) -> None:
        if pd is None:  # pragma: no cover - pandas is expected in this repo
            raise RuntimeError("pandas is required for PandasBackend")
        self._tables: dict[str, Any] = {}
        self._sequences: dict[str, int] = {}

    def ensure_model(self, model_cls: type["Model"]) -> None:
        name = model_cls._meta.model_name
        if name not in self._tables:
            self._tables[name] = pd.DataFrame(columns=["id", *model_cls._meta.field_order])
            self._sequences[name] = 0

    def _next_id(self, model_cls: type["Model"]) -> int:
        name = model_cls._meta.model_name
        self._sequences[name] = self._sequences.get(name, 0) + 1
        return self._sequences[name]

    def insert(self, model_cls: type["Model"], row: dict[str, Any]) -> dict[str, Any]:
        self.ensure_model(model_cls)
        row = copy.deepcopy(row)
        if row.get("id") is None:
            row["id"] = self._next_id(model_cls)
        frame = self._tables[model_cls._meta.model_name]
        if frame.empty:
            self._tables[model_cls._meta.model_name] = pd.DataFrame([row], columns=frame.columns)
        else:
            self._tables[model_cls._meta.model_name] = pd.concat([frame, pd.DataFrame([row])], ignore_index=True)
        return copy.deepcopy(row)

    def update(self, model_cls: type["Model"], record_id: int, row: dict[str, Any]) -> dict[str, Any]:
        self.ensure_model(model_cls)
        frame = self._tables[model_cls._meta.model_name]
        mask = frame["id"] == record_id
        if not mask.any():
            raise KeyError(f"record {record_id} not found")
        for key, value in row.items():
            frame.loc[mask, key] = value
        self._tables[model_cls._meta.model_name] = frame
        return copy.deepcopy(frame[mask].iloc[0].to_dict())

    def delete(self, model_cls: type["Model"], record_id: int) -> None:
        self.ensure_model(model_cls)
        frame = self._tables[model_cls._meta.model_name]
        self._tables[model_cls._meta.model_name] = frame[frame["id"] != record_id].reset_index(drop=True)

    def get(self, model_cls: type["Model"], record_id: int) -> dict[str, Any] | None:
        self.ensure_model(model_cls)
        frame = self._tables[model_cls._meta.model_name]
        match = frame[frame["id"] == record_id]
        if match.empty:
            return None
        return copy.deepcopy(match.iloc[0].to_dict())

    def all(self, model_cls: type["Model"]) -> list[dict[str, Any]]:
        self.ensure_model(model_cls)
        frame = self._tables[model_cls._meta.model_name]
        return [copy.deepcopy(row) for row in frame.sort_values("id").to_dict(orient="records")]


class SQLiteBackend(BackendAdapter):
    name = "sqlite"

    def __init__(self, path: str | Path = ":memory:") -> None:
        self.path = str(path)
        self._connection = sqlite3.connect(self.path)
        self._connection.row_factory = sqlite3.Row

    def ensure_model(self, model_cls: type["Model"]) -> None:
        table = model_cls._meta.table_name
        self._connection.execute(
            f'CREATE TABLE IF NOT EXISTS "{table}" (id INTEGER PRIMARY KEY AUTOINCREMENT, payload TEXT NOT NULL)'
        )
        self._connection.commit()

    def insert(self, model_cls: type["Model"], row: dict[str, Any]) -> dict[str, Any]:
        self.ensure_model(model_cls)
        cur = self._connection.execute(
            f'INSERT INTO "{model_cls._meta.table_name}" (payload) VALUES (?)',
            (json.dumps(row, default=_json_default),),
        )
        self._connection.commit()
        row = copy.deepcopy(row)
        row["id"] = cur.lastrowid
        self._connection.execute(
            f'UPDATE "{model_cls._meta.table_name}" SET payload = ? WHERE id = ?',
            (json.dumps(row, default=_json_default), row["id"]),
        )
        self._connection.commit()
        return row

    def update(self, model_cls: type["Model"], record_id: int, row: dict[str, Any]) -> dict[str, Any]:
        current = self.get(model_cls, record_id)
        if current is None:
            raise KeyError(f"record {record_id} not found")
        current.update(copy.deepcopy(row))
        current["id"] = int(record_id)
        self._connection.execute(
            f'UPDATE "{model_cls._meta.table_name}" SET payload = ? WHERE id = ?',
            (json.dumps(current, default=_json_default), int(record_id)),
        )
        self._connection.commit()
        return current

    def delete(self, model_cls: type["Model"], record_id: int) -> None:
        self.ensure_model(model_cls)
        self._connection.execute(f'DELETE FROM "{model_cls._meta.table_name}" WHERE id = ?', (int(record_id),))
        self._connection.commit()

    def get(self, model_cls: type["Model"], record_id: int) -> dict[str, Any] | None:
        self.ensure_model(model_cls)
        cur = self._connection.execute(
            f'SELECT payload FROM "{model_cls._meta.table_name}" WHERE id = ?',
            (int(record_id),),
        )
        row = cur.fetchone()
        if row is None:
            return None
        return copy.deepcopy(json.loads(row["payload"]))

    def all(self, model_cls: type["Model"]) -> list[dict[str, Any]]:
        self.ensure_model(model_cls)
        cur = self._connection.execute(f'SELECT payload FROM "{model_cls._meta.table_name}" ORDER BY id')
        return [copy.deepcopy(json.loads(row["payload"])) for row in cur.fetchall()]


class Repository:
    def __init__(self, model_cls: type["Model"], backend: BackendAdapter, env: "Env") -> None:
        self.model_cls = model_cls
        self.backend = backend
        self.env = env

    def create(self, values: dict[str, Any]) -> dict[str, Any]:
        row = self.model_cls._prepare_row(values, env=self.env)
        return self.backend.insert(self.model_cls, row)

    def update(self, record_id: int, values: dict[str, Any]) -> dict[str, Any]:
        row = self.model_cls._prepare_row(values, env=self.env, partial=True)
        return self.backend.update(self.model_cls, record_id, row)

    def delete(self, record_id: int) -> None:
        self.backend.delete(self.model_cls, record_id)

    def get(self, record_id: int) -> dict[str, Any] | None:
        return self.backend.get(self.model_cls, record_id)

    def all(self) -> list[dict[str, Any]]:
        return self.backend.all(self.model_cls)

    def search(self, conditions: Iterable[Condition]) -> list[dict[str, Any]]:
        condition_list = list(conditions)

        def predicate(row: dict[str, Any]) -> bool:
            return all(condition.evaluate(row, self.model_cls, self.env) for condition in condition_list)

        return self.backend.search(self.model_cls, predicate)


class RecordSet:
    def __init__(self, owner: type["Model"] | None, records: list["Model"] | None = None) -> None:
        self.owner = owner
        self._records = list(records or [])

    def __iter__(self) -> Iterator["Model"]:
        return iter(self._records)

    def __len__(self) -> int:
        return len(self._records)

    def __bool__(self) -> bool:
        return bool(self._records)

    def __getitem__(self, item: int | slice) -> "Model" | "RecordSet":
        if isinstance(item, slice):
            return RecordSet(self.owner, self._records[item])
        return self._records[item]

    def __repr__(self) -> str:
        model = self.owner.__name__ if self.owner else "RecordSet"
        return f"<{model}RecordSet {self.ids}>"

    def __getattr__(self, name: str) -> Any:
        if not self._records:
            raise AttributeError(name)
        attr = getattr(self._records[0], name)
        if callable(attr):

            def caller(*args: Any, **kwargs: Any) -> list[Any]:
                return [getattr(record, name)(*args, **kwargs) for record in self._records]

            return caller
        if len(self._records) == 1:
            return attr
        return [getattr(record, name) for record in self._records]

    @property
    def ids(self) -> list[int]:
        return [record.id for record in self._records if record.id is not None]

    def first(self) -> "Model" | None:
        return self._records[0] if self._records else None

    def all(self) -> list["Model"]:
        return list(self._records)

    def mapped(self, name: str) -> list[Any]:
        return [getattr(record, name) for record in self._records]

    def filtered(self, predicate: Callable[["Model"], bool]) -> "RecordSet":
        return RecordSet(self.owner, [record for record in self._records if predicate(record)])

    def update(self, **values: Any) -> "RecordSet":
        for record in self._records:
            for key, value in values.items():
                setattr(record, key, value)
            record.save()
        return self

    def delete(self) -> None:
        for record in list(self._records):
            record.delete()
        self._records.clear()


class QuerySet:
    def __init__(
        self,
        model_cls: type["Model"],
        env: "Env",
        *,
        conditions: list[Condition] | None = None,
        orderings: list[str] | None = None,
        limit_value: int | None = None,
    ) -> None:
        self.model_cls = model_cls
        self.env = env
        self.conditions = list(conditions or [])
        self.orderings = list(orderings or [])
        self.limit_value = limit_value

    def _clone(self, **kwargs: Any) -> "QuerySet":
        return QuerySet(
            self.model_cls,
            self.env,
            conditions=kwargs.get("conditions", self.conditions),
            orderings=kwargs.get("orderings", self.orderings),
            limit_value=kwargs.get("limit_value", self.limit_value),
        )

    def _repository(self) -> Repository:
        return Repository(self.model_cls, self.env.backend, self.env)

    def _evaluate_rows(self) -> list[dict[str, Any]]:
        rows = self._repository().search(self.conditions) if self.conditions else self._repository().all()
        if self.orderings:
            rows = self._apply_ordering(rows)
        if self.limit_value is not None:
            rows = rows[: self.limit_value]
        return rows

    def _apply_ordering(self, rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
        ordered = list(rows)
        for ordering in reversed(self.orderings):
            reverse = ordering.startswith("-")
            field_name = ordering[1:] if reverse else ordering

            def sort_key(row: dict[str, Any]) -> tuple[bool, Any]:
                value = row.get(field_name)
                return (value is None, value)

            ordered.sort(key=sort_key, reverse=reverse)
        return ordered

    def _records(self) -> list["Model"]:
        return [self.model_cls._from_row(row, env=self.env) for row in self._evaluate_rows()]

    def filter(self, *domain: Any, **lookups: Any) -> "QuerySet":
        conditions = self.conditions + _build_conditions(*domain, **lookups)
        return self._clone(conditions=conditions)

    def exclude(self, *domain: Any, **lookups: Any) -> "QuerySet":
        conditions = self.conditions + [
            Condition(condition.field_name, condition.lookup, condition.value, not condition.negated)
            for condition in _build_conditions(*domain, **lookups)
        ]
        return self._clone(conditions=conditions)

    def search(self, domain: Iterable[tuple[str, str, Any]] | None = None, **lookups: Any) -> "RecordSet":
        return self.filter(*(list(domain or [])), **lookups).all()

    def order_by(self, *fields: str) -> "QuerySet":
        return self._clone(orderings=self.orderings + list(fields))

    def limit(self, value: int) -> "QuerySet":
        return self._clone(limit_value=int(value))

    def all(self) -> RecordSet:
        return RecordSet(self.model_cls, self._records())

    def first(self) -> "Model" | None:
        return self.all().first()

    def create(self, values: dict[str, Any] | None = None, **kwargs: Any) -> "Model" | RecordSet:
        payload = dict(values or {})
        payload.update(kwargs)
        return self.model_cls.create(payload, env=self.env)

    def update(self, **values: Any) -> RecordSet:
        records = self.all()
        records.update(**values)
        return records

    def delete(self) -> None:
        self.all().delete()

    def browse(self, ids: int | Iterable[int]) -> "Model" | RecordSet | None:
        return self.model_cls.browse(ids, env=self.env)

    def __iter__(self) -> Iterator["Model"]:
        return iter(self.all())

    def __len__(self) -> int:
        return len(self.all())


class Manager:
    def __init__(self, model_cls: type["Model"] | None = None, env: "Env" | None = None) -> None:
        self.model_cls = model_cls
        self.env = env

    def __get__(self, instance: "Model" | None, owner: type["Model"]) -> "Manager":
        env = owner._env or default_env
        return Manager(owner, env)

    def get_queryset(self) -> QuerySet:
        if self.model_cls is None or self.env is None:
            raise RuntimeError("manager is not bound to a model")
        return QuerySet(self.model_cls, self.env)

    def filter(self, *domain: Any, **lookups: Any) -> QuerySet:
        return self.get_queryset().filter(*domain, **lookups)

    def exclude(self, *domain: Any, **lookups: Any) -> QuerySet:
        return self.get_queryset().exclude(*domain, **lookups)

    def order_by(self, *fields: str) -> QuerySet:
        return self.get_queryset().order_by(*fields)

    def limit(self, value: int) -> QuerySet:
        return self.get_queryset().limit(value)

    def all(self) -> RecordSet:
        return self.get_queryset().all()

    def first(self) -> "Model" | None:
        return self.get_queryset().first()

    def create(self, values: dict[str, Any] | None = None, **kwargs: Any) -> "Model" | RecordSet:
        return self.get_queryset().create(values, **kwargs)

    def search(self, domain: Iterable[tuple[str, str, Any]] | None = None, **lookups: Any) -> RecordSet:
        return self.get_queryset().search(domain, **lookups)

    def browse(self, ids: int | Iterable[int]) -> "Model" | RecordSet | None:
        return self.get_queryset().browse(ids)


class ModelProxy:
    def __init__(self, model_cls: type["Model"], env: "Env") -> None:
        self.model_cls = model_cls
        self.env = env

    @property
    def objects(self) -> Manager:
        return Manager(self.model_cls, self.env)

    def create(self, values: dict[str, Any] | None = None, **kwargs: Any) -> "Model" | RecordSet:
        return self.model_cls.create(values, env=self.env, **kwargs)

    def search(self, domain: Iterable[tuple[str, str, Any]] | None = None, **lookups: Any) -> RecordSet:
        return self.model_cls.search(domain, env=self.env, **lookups)

    def browse(self, ids: int | Iterable[int]) -> "Model" | RecordSet | None:
        return self.model_cls.browse(ids, env=self.env)

    def all(self) -> RecordSet:
        return self.model_cls.all(env=self.env)

    def __getattr__(self, name: str) -> Any:
        manager = self.objects
        if hasattr(manager, name):
            return getattr(manager, name)
        return getattr(self.model_cls, name)


class Env:
    def __init__(self, registry: Registry | None = None, backend: BackendAdapter | None = None) -> None:
        self.registry = registry or default_registry
        self.backend = backend or MemoryBackend()
        self.registry.bind_env(self)

    def __getitem__(self, model_name: str) -> ModelProxy:
        return ModelProxy(self.registry.models[model_name], self)

    def repository(self, model_cls: type["Model"]) -> Repository:
        return Repository(model_cls, self.backend, self)


class UnitOfWork:
    def __init__(self, env: Env) -> None:
        self.env = env
        self.new: list[Model] = []
        self.dirty: list[Model] = []
        self.deleted: list[Model] = []

    def register_new(self, record: "Model") -> "Model":
        self.new.append(record)
        return record

    def register_dirty(self, record: "Model") -> "Model":
        self.dirty.append(record)
        return record

    def register_deleted(self, record: "Model") -> "Model":
        self.deleted.append(record)
        return record

    def commit(self) -> None:
        for record in self.new:
            record.save()
        for record in self.dirty:
            record.save()
        for record in self.deleted:
            record.delete()
        self.rollback()

    def rollback(self) -> None:
        self.new.clear()
        self.dirty.clear()
        self.deleted.clear()


class Session:
    def __init__(self, env: Env | None = None) -> None:
        self.env = env or default_env
        self.uow = UnitOfWork(self.env)

    def __enter__(self) -> "Session":
        return self

    def __exit__(self, exc_type, exc, tb) -> None:
        if exc is None:
            self.commit()
        else:
            self.rollback()

    def add(self, record: "Model") -> "Model":
        return self.uow.register_new(record)

    def dirty(self, record: "Model") -> "Model":
        return self.uow.register_dirty(record)

    def delete(self, record: "Model") -> "Model":
        return self.uow.register_deleted(record)

    def commit(self) -> None:
        self.uow.commit()

    def rollback(self) -> None:
        self.uow.rollback()


default_env: Env


def _build_conditions(*domain: Any, **lookups: Any) -> list[Condition]:
    conditions: list[Condition] = []
    for item in domain:
        if isinstance(item, Condition):
            conditions.append(item)
            continue
        if isinstance(item, (list, tuple)) and len(item) == 3:
            field_name, lookup, value = item
            conditions.append(Condition(str(field_name), str(lookup), value))
            continue
        if isinstance(item, (list, tuple)):
            conditions.extend(_build_conditions(*item))
            continue
        raise TypeError(f"unsupported domain item: {item!r}")

    for key, value in lookups.items():
        parts = key.split("__")
        if parts[-1] in LOOKUPS:
            field_name = "__".join(parts[:-1])
            lookup = parts[-1]
        else:
            field_name = key
            lookup = "eq"
        lookup = LOOKUP_ALIASES.get(lookup, lookup)
        conditions.append(Condition(field_name, lookup, value))
    return conditions


class ModelMeta(type):
    def __new__(mcls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any]) -> type[Any]:
        attrs = dict(attrs)
        inherited_fields: OrderedDict[str, Field] = OrderedDict()
        for base in bases:
            base_meta = getattr(base, "_meta", None)
            if base_meta is None:
                continue
            for field_name, field_obj in base_meta.fields.items():
                if field_name in attrs:
                    continue
                inherited_fields[field_name] = field_obj.clone()
        attrs.update(inherited_fields)
        cls = super().__new__(mcls, name, bases, attrs)

        collected_fields: OrderedDict[str, Field] = OrderedDict()
        for base in bases:
            base_meta = getattr(base, "_meta", None)
            if base_meta is None:
                continue
            for field_name, field_obj in base_meta.fields.items():
                collected_fields[field_name] = field_obj.clone()
        for attr_name, attr_value in cls.__dict__.items():
            if isinstance(attr_value, Field):
                collected_fields[attr_name] = attr_value

        model_name = attrs.get("_name")
        inherit = attrs.get("_inherit")
        abstract = bool(attrs.get("_abstract", False) or name == "Model")
        table_name = str(model_name or name.lower())
        cls._meta = ModelOptions(
            model_name=str(model_name or ""),
            table_name=table_name,
            abstract=abstract,
            fields=collected_fields,
            field_order=list(collected_fields.keys()),
        )
        cls._registry = default_registry
        cls._env = None
        cls.objects = Manager()

        if inherit and not model_name:
            default_registry.extend(cls)
            return cls

        if not abstract:
            if not model_name:
                cls._meta.model_name = name.lower()
                cls._meta.table_name = name.lower()
            default_registry.register(cls)
        return cls


class Model(metaclass=ModelMeta):
    _abstract = True
    _name: str = ""
    _inherit: str | list[str] | None = None
    _registry: Registry
    _env: Env | None
    _meta: ModelOptions
    objects: Manager

    def __init__(self, env: Env | None = None, id: int | None = None, **values: Any) -> None:
        self._env = env or self.__class__._env or default_env
        self._values: dict[str, Any] = {}
        self._persisted = False
        self.id = id
        for key, value in values.items():
            setattr(self, key, value)

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} id={self.id!r}>"

    @classmethod
    def _get_env(cls, env: Env | None = None) -> Env:
        resolved = env or cls._env or default_env
        if resolved is None:
            raise RuntimeError("no ORM environment is available")
        return resolved

    @classmethod
    def _repository(cls, env: Env | None = None) -> Repository:
        resolved_env = cls._get_env(env)
        return Repository(cls, resolved_env.backend, resolved_env)

    @classmethod
    def _prepare_row(cls, values: dict[str, Any], env: Env, *, partial: bool = False) -> dict[str, Any]:
        row: dict[str, Any] = {}
        provided = dict(values)
        for field_name, field_obj in cls._meta.fields.items():
            if not field_obj.store:
                continue
            if field_name in provided:
                raw_value = provided.pop(field_name)
            else:
                if partial:
                    continue
                raw_value = field_obj.get_default(None)
            validated = field_obj.validate(raw_value)
            row[field_name] = field_obj.to_storage(validated)
        if provided and not partial:
            unknown = ", ".join(sorted(provided))
            raise AttributeError(f"unknown fields for {cls.__name__}: {unknown}")
        if partial:
            for key, value in provided.items():
                field_obj = cls._meta.fields.get(key)
                if field_obj is None or not field_obj.store:
                    continue
                row[key] = field_obj.to_storage(field_obj.validate(value))
        if "id" in values and values["id"] is not None:
            row["id"] = int(values["id"])
        return row

    @classmethod
    def _from_row(cls, row: dict[str, Any], env: Env) -> "Model":
        obj = cls.__new__(cls)
        obj._env = env
        obj._values = {}
        obj._persisted = True
        obj.id = row.get("id")
        for field_name, field_obj in cls._meta.fields.items():
            if field_name not in row:
                continue
            obj._values[field_name] = field_obj.from_storage(row[field_name], obj)
        return obj

    @classmethod
    def create(cls, values: dict[str, Any] | None = None, *, env: Env | None = None, **kwargs: Any) -> "Model" | RecordSet:
        payload = dict(values or {})
        payload.update(kwargs)
        resolved_env = cls._get_env(env)
        row = cls._repository(resolved_env).create(payload)
        return cls._from_row(row, resolved_env)

    @classmethod
    def browse(cls, ids: int | Iterable[int], *, env: Env | None = None) -> "Model" | RecordSet | None:
        resolved_env = cls._get_env(env)
        repo = cls._repository(resolved_env)
        if isinstance(ids, Iterable) and not isinstance(ids, (str, bytes)):
            records = []
            for record_id in ids:
                row = repo.get(int(record_id))
                if row is not None:
                    records.append(cls._from_row(row, resolved_env))
            return RecordSet(cls, records)
        row = repo.get(int(ids))
        return cls._from_row(row, resolved_env) if row is not None else None

    @classmethod
    def search(cls, domain: Iterable[tuple[str, str, Any]] | None = None, *, env: Env | None = None, **lookups: Any) -> RecordSet:
        resolved_env = cls._get_env(env)
        return QuerySet(cls, resolved_env).search(domain, **lookups)

    @classmethod
    def all(cls, *, env: Env | None = None) -> RecordSet:
        return QuerySet(cls, cls._get_env(env)).all()

    def to_dict(self, *, raw: bool = False) -> dict[str, Any]:
        payload: dict[str, Any] = {"id": self.id}
        for field_name, field_obj in self._meta.fields.items():
            if not field_obj.store:
                continue
            value = self._values.get(field_name)
            payload[field_name] = value if raw else field_obj.from_storage(value, self)
        return payload

    def save(self) -> "Model":
        env = self._env or default_env
        repo = self._repository(env)
        payload = self.to_dict(raw=True)
        payload.pop("id", None)
        if self.id is None:
            row = repo.create(payload)
            self.id = row["id"]
            self._values.update({k: v for k, v in row.items() if k != "id"})
            self._persisted = True
            return self
        row = repo.update(int(self.id), payload)
        self._values.update({k: v for k, v in row.items() if k != "id"})
        self._persisted = True
        return self

    def delete(self) -> None:
        if self.id is None:
            return
        repo = self._repository(self._env or default_env)
        repo.delete(int(self.id))
        self.id = None
        self._persisted = False

    def refresh(self) -> "Model":
        if self.id is None:
            raise RuntimeError("cannot refresh an unsaved record")
        row = self._repository(self._env or default_env).get(int(self.id))
        if row is None:
            raise KeyError(f"record {self.id} not found")
        fresh = self.__class__._from_row(row, self._env or default_env)
        self._values = fresh._values
        self._persisted = True
        return self

    def copy(self, **overrides: Any) -> "Model":
        payload = self.to_dict(raw=False)
        payload.pop("id", None)
        payload.update(overrides)
        return self.__class__.create(payload, env=self._env or default_env)

    def __setattr__(self, name: str, value: Any) -> None:
        if name in {"_env", "_values", "_persisted", "id"}:
            object.__setattr__(self, name, value)
            return
        meta = getattr(self.__class__, "_meta", None)
        if meta is not None and name in meta.fields:
            meta.fields[name].__set__(self, value)
            return
        object.__setattr__(self, name, value)


fields = SimpleNamespace(
    Field=Field,
    String=String,
    Integer=Integer,
    Float=Float,
    Boolean=Boolean,
    Date=Date,
    DateTime=DateTime,
    Text=Text,
    ForeignKey=ForeignKey,
    OneToMany=OneToMany,
    ManyToMany=ManyToMany,
)


default_env = Env(default_registry, MemoryBackend())
