"""Base.to_dict()/__repr__ caching + behavior.

These run once per row when serializing ORM rows (e.g. every event in a
messages page), so the mapped-column reflection is cached per class. Behavior
must stay identical.
"""

from __future__ import annotations

from sqlalchemy import Integer, MetaData, String
from sqlalchemy.orm import Mapped, mapped_column

from deerflow.persistence.base import Base, _column_keys


class _Widget(Base):
    __tablename__ = "_widget_to_dict_test"
    # Keep this test-only model out of the application metadata. Pytest imports
    # test modules during collection, so registering it on ``Base.metadata``
    # would leak the table into unrelated create_all/schema-parity tests.
    metadata = MetaData()

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(32))
    color: Mapped[str] = mapped_column(String(16))


def test_to_dict_returns_all_columns():
    w = _Widget(id=1, name="gear", color="red")
    assert w.to_dict() == {"id": 1, "name": "gear", "color": "red"}


def test_to_dict_exclude():
    w = _Widget(id=2, name="cog", color="blue")
    assert w.to_dict(exclude={"color"}) == {"id": 2, "name": "cog"}
    # Empty exclude behaves like no exclude.
    assert w.to_dict(exclude=set()) == {"id": 2, "name": "cog", "color": "blue"}


def test_column_keys_are_cached_per_class():
    # Same tuple object returned across calls -> reflection ran once.
    assert _column_keys(_Widget) is _column_keys(_Widget)
    assert _column_keys(_Widget) == ("id", "name", "color")


def test_repr_lists_columns():
    w = _Widget(id=3, name="bolt", color="green")
    r = repr(w)
    assert r.startswith("_Widget(")
    assert "id=3" in r and "name='bolt'" in r and "color='green'" in r
