"""Edge-case coverage for DataTable internals and error branches."""

from collections import namedtuple
import logging
import sys
import types
from pathlib import Path

import pytest

import soigia.datatable as dtpkg
from soigia.datatable import DataTable


def test_values_result_branches():
    records = dtpkg._ValuesResult(
        [
            {"name": "alice", "age": 19},
            {"name": "bob", "age": 24},
            {"name": "alice", "age": 19},
            ("x", "y"),
            ["x", "y"],
            123,
        ]
    )

    assert records.filter(lambda row: isinstance(row, dict) and row.get("name") == "alice") == [
        {"name": "alice", "age": 19},
        {"name": "alice", "age": 19},
    ]
    assert records.exclude(lambda row: isinstance(row, dict) and row.get("name") == "alice") == [
        {"name": "bob", "age": 24},
        ("x", "y"),
        ["x", "y"],
        123,
    ]
    assert records.order_by(lambda row: str(row)) == _sorted_records(records, key=lambda row: str(row))
    assert dtpkg._ValuesResult([1, 1, 2, 3, 2]).distinct() == [1, 2, 3]
    assert dtpkg._ValuesResult([{"a": 1}, {"a": 1}, {"a": 2}]).distinct("a") == [{"a": 1}, {"a": 2}]

    with pytest.raises(TypeError):
        records.filter(predicate="bad")
    with pytest.raises(TypeError):
        records.exclude(predicate="bad")
    with pytest.raises(TypeError):
        records.order_by(123)


def _sorted_records(records, key):
    return dtpkg._ValuesResult(sorted(records, key=key))


def test_no_logs_context_branches():
    ctx = dtpkg._NoLogsContext("soigia.edge", level=logging.INFO)
    with ctx:
        pass

    ctx2 = dtpkg._NoLogsContext("soigia.edge", level=logging.INFO)
    with pytest.raises(AssertionError):
        with ctx2:
            logging.getLogger("soigia.edge").warning("hello")


def test_mini_faker_and_resolve_fake_value_branches():
    fake = dtpkg._MiniFaker(seed=123)
    fake.seed_instance(123)
    assert fake.name()
    assert fake.first_name()
    assert fake.last_name()
    assert fake.city()
    assert fake.company()
    assert fake.word()
    assert fake.words(2)
    assert fake.sentence(4).endswith(".")
    assert fake.text(20)
    assert "@" in fake.email()
    assert fake.phone_number().startswith("+84-")
    assert isinstance(fake.uuid4(), str)
    assert isinstance(fake.random_int(1, 3), int)
    assert isinstance(fake.pyfloat(), float)
    assert fake.date().tzinfo is not None
    assert fake.datetime().tzinfo is not None

    assert dtpkg._resolve_fake_value(fake, lambda gen: gen.name()) != ""
    assert dtpkg._resolve_fake_value(fake, ("name", {}))
    assert dtpkg._resolve_fake_value(fake, {"provider": "city"}) in fake._cities
    assert dtpkg._resolve_fake_value(fake, "name")
    assert dtpkg._resolve_fake_value(fake, 123) == 123


def test_factory_conversion_branches():
    Point = namedtuple("Point", "x y")

    class Dictable:
        def dict(self):
            return {"name": "dictable"}

    class AttrObject:
        def __init__(self):
            self.name = "attr"
            self.age = 99
            self._private = "hidden"

    assert dtpkg._factory_item_to_record({"name": "alice"}) == {"name": "alice"}
    assert dtpkg._factory_item_to_record(Point(1, 2)) == {"x": 1, "y": 2}
    assert dtpkg._factory_item_to_record(Dictable()) == {"name": "dictable"}
    assert dtpkg._factory_item_to_record(AttrObject()) == {"name": "attr", "age": 99}

    with pytest.raises(TypeError):
        dtpkg._factory_item_to_record(123)

    class BatchFactory:
        @classmethod
        def build_batch(cls, size=1, **kwargs):
            return [{"name": f"row_{i}", "kind": kwargs.get("kind", "a")} for i in range(size)]

    assert dtpkg._build_factory_rows(BatchFactory, 2, kind="b") == [
        {"name": "row_0", "kind": "b"},
        {"name": "row_1", "kind": "b"},
    ]
    assert dtpkg._build_factory_rows(lambda **kwargs: {"name": kwargs.get("name", "x")}, 2, name="y") == [
        {"name": "y"},
        {"name": "y"},
    ]
    with pytest.raises(TypeError):
        dtpkg._build_factory_rows(object(), 1)


def test_database_connector_branches(monkeypatch):
    connector = dtpkg.DatabaseConnector(connection_string="sqlite:///example.db", mysql="mysql:///mysql.db")
    assert connector._engine_url("mysql") == "mysql:///mysql.db"
    assert connector._engine_url("postgres", connection_string="postgresql:///db") == "postgresql:///db"

    monkeypatch.setenv("SOIGIA_POSTGRES_URL", "postgresql:///envdb")
    connector2 = dtpkg.DatabaseConnector()
    assert connector2._engine_url("postgres") == "postgresql:///envdb"

    with pytest.raises(ValueError):
        dtpkg.DatabaseConnector()._engine_url("oracle")

    monkeypatch.setattr(dtpkg, "create_engine", None)
    with pytest.raises(ImportError):
        connector2._engine("sqlite")


def test_join_and_schema_error_branches():
    left = DataTable({"id": [1, 2], "name": ["alice", "bob"], "age": ["19", "24"]})
    right = DataTable({"id": [1, 2], "city": ["hn", "sg"]})

    assert left.join(right, on="id").shape[0] == 2
    assert left.join(right, left_on="id", right_on="id").shape[0] == 2
    assert left.join(DataTable({"id": [1], "city": ["hn"]})).shape[0] == 1

    with pytest.raises(ValueError):
        left.join(right, on=[])
    with pytest.raises(ValueError):
        left.join(right, left_on="id")
    with pytest.raises(ValueError):
        left.join(right, left_on=["id"], right_on=["id", "extra"])
    with pytest.raises(ValueError):
        DataTable({"a": [1]}).join(DataTable({"b": [1]}))

    with pytest.raises(ValueError):
        left.validate_schema({"extra": {"required": True}}, strict=True)
    with pytest.raises(KeyError):
        left.validate_schema({"missing": {"required": True}})
    with pytest.raises(ValueError):
        DataTable({"name": ["alice", None]}).validate_schema({"name": {"nullable": False}})
    with pytest.raises(TypeError):
        left.validate_schema({"age": {"type": int}})
    numeric = DataTable({"age": [19, 24]})
    with pytest.raises(ValueError):
        left.validate_schema({"name": {"choices": ["carol"]}})
    with pytest.raises(ValueError):
        numeric.validate_schema({"age": {"min": 20}})
    with pytest.raises(ValueError):
        numeric.validate_schema({"age": {"max": 20}})
    with pytest.raises(ValueError):
        left.validate_schema({"name": {"regex": "^z"}})
    with pytest.raises(ValueError):
        left.validate_schema({"name": {"length": 2}})


def test_type_coercion_render_and_snapshot_branches():
    table = DataTable({"age": ["19", "24"], "created_at": ["2026-01-01", "2026-01-02"]})
    coerced = table.coerce_types({"age": "int64", "created_at": "datetime"})
    assert coerced["age"].dtype.kind in {"i", "u"}
    assert str(coerced["created_at"].dtype).startswith("datetime64")

    coerced_date = table.coerce_types({"created_at": "date"})
    assert coerced_date["created_at"].iloc[0].isoformat() == "2026-01-01"

    with pytest.raises(KeyError):
        table.coerce_types({"missing": "int64"})

    with pytest.raises(ValueError):
        table.render_table("excel")

    with pytest.raises(ValueError):
        table.render_table("unknown")

    table.init_versions("initial")
    snapshot = table.snapshot("snap")
    assert snapshot.version_count() == 2
    assert snapshot.current_version()["label"] == "snap"

    auto = table.auto_version("auto")
    assert auto.is_auto_versioning() is True
    assert auto.disable_auto_version().is_auto_versioning() is False


def test_manager_delegation_branches():
    table = DataTable({"id": [1, 2, 3], "name": ["alice", "bob", "carol"], "age": [19, 24, 31]})
    manager = table.objects

    assert manager.all().count() == 3
    assert manager.none().count() == 0
    assert manager.values_list("name", flat=True).first() == "alice"
    assert manager.select("name").to_df().columns.tolist() == ["name"]
    assert manager.exclude_fields("age").to_df().columns.tolist() == ["id", "name"]
    assert manager.get(name="bob")["age"].iloc[0] == 24
    assert manager.first()["name"].iloc[0] == "alice"
    assert manager.last()["name"].iloc[0] == "carol"
    assert manager.earliest("age")["name"].iloc[0] == "alice"
    assert manager.latest("age")["name"].iloc[0] == "carol"
    assert manager.paginate(limit=2).count() == 2
    assert manager.group_by("name").count().count() == 3
    assert manager.aggregate(total=("age", "sum")) == {"total": 74}
    assert manager.sum("age") == {"age": 74}
    assert manager.avg("age") == {"age": 24.666666666666668}
    assert manager.min("age") == {"age": 19}
    assert manager.max("age") == {"age": 31}
    assert manager.values("name").first() == {"name": "alice"}
    assert manager.diff_columns_against(table)["column_order_changed"] is False
    assert manager.diff_added_rows(table).shape[0] == 0
    assert manager.diff_removed_rows(table).shape[0] == 0
    assert manager.diff_unchanged_rows(table).shape[0] == 3
    assert manager.diff_changed_rows(table).shape[0] == 0
    assert manager.diff_changed_cells(table).shape[0] == 0
    assert manager.diff_report(table)["has_changes"] is False

    created = manager.create(name="dave", age=40)
    assert created.to_rows() == [{"name": "dave", "age": 40}]
    assert manager.append_row(name="dave", age=40).shape[0] == 4
    assert manager.bulk_create([{"name": "x", "age": 1}]).shape[0] == 4
    assert manager.update(age=25) == 3
    assert manager.delete() == 3
    updated_df, count = manager.bulk_update({"age": 25}, return_count=True, age__gte=20)
    assert count == 2
    assert updated_df.shape[0] == 3
    assert manager.save().equals(table)
    assert manager.get_or_create(name="bob", defaults={"age": 24})[1] is False
    assert manager.update_or_create(name="new", defaults={"age": 18})[1] is True

    joined = manager.join(DataTable({"id": [1, 2, 3], "city": ["hn", "sg", "dn"]}), on="id").to_df()
    assert "city" in joined.columns


def test_fake_faker_and_csv_from_helpers(monkeypatch):
    fake_module = types.ModuleType("faker")

    class FakeFaker:
        def __init__(self, locale):
            self.locale = locale
            self.seeded = None

        def seed_instance(self, seed):
            self.seeded = seed

        def name(self):
            return "fake name"

        def city(self):
            return "fake city"

    fake_module.Faker = FakeFaker
    monkeypatch.setitem(sys.modules, "faker", fake_module)

    fake = dtpkg._build_faker(locale="vi_VN", seed=7)
    assert isinstance(fake, FakeFaker)
    assert fake.locale == "vi_VN"
    assert fake.seeded == 7
    assert dtpkg._resolve_fake_value(fake, ("name", {})) == "fake name"
    assert dtpkg._resolve_fake_value(fake, {"provider": "city"}) == "fake city"
    assert dtpkg._resolve_fake_value(fake, "name") == "fake name"
    assert dtpkg._resolve_fake_value(fake, 123) == 123

    values = dtpkg._ValuesResult([("x", "y"), ["x", "y"], 123, {"a": 1}, {"a": 1}])
    assert values.distinct() == [("x", "y"), 123, {"a": 1}]
    assert dtpkg._ValuesResult([{"a": 1}, {"a": 1}, {"a": 2}]).distinct("a") == [{"a": 1}, {"a": 2}]

    csv_path = Path("tests") / "_datatable_coverage.csv"
    csv_path.write_text("a,b\n1,2\n", encoding="utf-8")
    try:
        loaded = DataTable.from_csv(csv_path)
        assert loaded.to_dict(orient="records") == [{"a": 1, "b": 2}]
        assert DataTable.from_dict({"x": [1, 2]}).shape == (2, 1)
    finally:
        if csv_path.exists():
            csv_path.unlink()


def test_database_helpers_and_classmethod_wrappers(monkeypatch):
    calls = []

    def fake_query(self, sql, backend, connection_string=None, **kwargs):
        calls.append((backend, sql, connection_string, kwargs))
        return DataTable({"backend": [backend], "sql": [sql]})

    monkeypatch.setattr(dtpkg.DatabaseConnector, "query", fake_query)

    connector = dtpkg.DatabaseConnector(
        connection_string="sqlite:///base.db",
        mysql="mysql:///db",
        maria="maria:///db",
        oracle="oracle:///db",
        postgres="postgres:///db",
        sqlite="sqlite:///db",
    )
    assert connector.from_sql("select 1").iloc[0]["backend"] == "sqlite"
    assert connector.from_mysql_db("select 1").iloc[0]["backend"] == "mysql"
    assert connector.from_maria_db("select 1").iloc[0]["backend"] == "maria"
    assert connector.from_oracle_db("select 1").iloc[0]["backend"] == "oracle"
    assert connector.from_postgres_db("select 1").iloc[0]["backend"] == "postgres"
    assert connector.from_sqlite_db("select 1").iloc[0]["backend"] == "sqlite"

    default = dtpkg.db_connector(connection_string="sqlite:///default.db")
    assert isinstance(default, dtpkg.DatabaseConnector)
    monkeypatch.setattr(dtpkg, "_DEFAULT_CONNECTOR", connector)

    assert dtpkg.from_sql("select 1").iloc[0]["backend"] == "sqlite"
    assert dtpkg.from_mysql_db("select 1").iloc[0]["backend"] == "mysql"
    assert dtpkg.from_maria_db("select 1").iloc[0]["backend"] == "maria"
    assert dtpkg.from_oracle_db("select 1").iloc[0]["backend"] == "oracle"
    assert dtpkg.from_postgres_db("select 1").iloc[0]["backend"] == "postgres"
    assert dtpkg.from_sqlite_db("select 1").iloc[0]["backend"] == "sqlite"

    monkeypatch.setattr(dtpkg, "from_sql", lambda sql, backend="sqlite", connection_string=None, **kwargs: DataTable({"kind": [backend]}))
    monkeypatch.setattr(dtpkg, "from_mysql_db", lambda sql, connection_string=None, **kwargs: DataTable({"kind": ["mysql"]}))
    monkeypatch.setattr(dtpkg, "from_maria_db", lambda sql, connection_string=None, **kwargs: DataTable({"kind": ["maria"]}))
    monkeypatch.setattr(dtpkg, "from_oracle_db", lambda sql, connection_string=None, **kwargs: DataTable({"kind": ["oracle"]}))
    monkeypatch.setattr(dtpkg, "from_postgres_db", lambda sql, connection_string=None, **kwargs: DataTable({"kind": ["postgres"]}))
    monkeypatch.setattr(dtpkg, "from_sqlite_db", lambda sql, connection_string=None, **kwargs: DataTable({"kind": ["sqlite"]}))

    assert DataTable.from_sql("select 1")["kind"].iloc[0] == "sqlite"
    assert DataTable.from_mysql_db("select 1")["kind"].iloc[0] == "mysql"
    assert DataTable.from_maria_db("select 1")["kind"].iloc[0] == "maria"
    assert DataTable.from_oracle_db("select 1")["kind"].iloc[0] == "oracle"
    assert DataTable.from_postgres_db("select 1")["kind"].iloc[0] == "postgres"
    assert DataTable.from_sqlite_db("select 1")["kind"].iloc[0] == "sqlite"

    assert len(calls) >= 6


def test_deep_diff_and_version_branches(monkeypatch):
    class WeirdEq:
        def __eq__(self, other):
            return Boolish()

    class Boolish:
        def __bool__(self):
            return True

    class EqRaises:
        def __eq__(self, other):
            raise RuntimeError("boom")

        def __repr__(self):
            return "<eq-raises>"

    monkeypatch.setattr(dtpkg.pd, "isna", lambda value: False)
    assert dtpkg.DataTable._cell_equal(WeirdEq(), WeirdEq()) is True
    assert dtpkg.DataTable._cell_equal(EqRaises(), EqRaises()) is True

    class NaRaises:
        pass

    monkeypatch.setattr(dtpkg.pd, "isna", lambda value: (_ for _ in ()).throw(RuntimeError("na")))
    assert dtpkg.DataTable._cell_equal(NaRaises(), NaRaises()) is False

    base = DataTable({"id": [1, 2], "name": ["alice", "bob"], "age": ["19", "24"]})
    base.init_versions("start")
    assert "records" not in base.version_history()[0]
    assert base.version_history(include_data=True)[0]["records"] == [{"id": 1, "name": "alice", "age": "19"}, {"id": 2, "name": "bob", "age": "24"}]

    auto_base = DataTable({"id": [1, 2], "name": ["alice", "bob"], "age": ["19", "24"]}).auto_version("auto")
    grown = auto_base.join(DataTable({"id": [1, 2, 3], "city": ["hn", "sg", "dn"]}), on="id", how="outer")
    shrunk = auto_base.join(DataTable({"id": [1], "city": ["hn"]}), on="id", how="inner")

    assert auto_base.is_auto_versioning() is True
    assert auto_base.disable_auto_version().is_auto_versioning() is False
    assert auto_base.version_count() == 1
    assert grown.version_count() == 2
    assert grown.version_history()[0]["label"] == "initial"
    assert grown.current_version()["label"] == "auto"
    assert grown.get_version(0).shape == (2, 3)
    assert grown.get_version(-1).shape == (3, 4)
    assert grown.restore_version(0).equals(grown.get_version(0))

    assert grown.diff_versions() ["row_delta"] == 1
    assert grown.diff_records()["row_delta"] == 1
    assert grown.diff_columns()["column_order_changed"] is True
    assert grown.diff_summary(key_columns=["id"])["changed_rows"] >= 0

    keyed_cells = grown.diff_cells(key_columns=["id"])
    assert isinstance(keyed_cells, dict)
    assert grown.diff_cells(old=0, new=-1, key_columns=["id"])["added_rows"]
    assert shrunk.diff_cells(old=0, new=-1)["removed_rows"]
    with pytest.raises(KeyError):
        grown.diff_cells(key_columns=["missing"])

    report = grown.diff_report(shrunk, key_columns=["id"])
    assert report["has_changes"] is True
    assert "column_order_changed" in report["column_diff"]
    assert report["same_rows"].shape[0] >= 0
    assert report["changed_cells"].shape[0] >= 0

    comparison = grown.compare_frames(shrunk, key_columns=["id"])
    assert comparison["same_count"] >= 0
    assert grown.same_rows(shrunk, key_columns=["id"]).shape[0] >= 0
    diff_rows = grown.different_rows(shrunk, key_columns=["id"])
    assert diff_rows["changed_count"] >= 0
    assert grown.diff_rows(shrunk, key_columns=["id"])["left_only_count"] >= 0
    assert "shared_columns" in grown.diff_columns_against(shrunk)
    assert grown.diff_added_rows(shrunk, key_columns=["id"]).shape[0] >= 0
    assert grown.diff_removed_rows(shrunk, key_columns=["id"]).shape[0] >= 0
    assert grown.diff_unchanged_rows(shrunk, key_columns=["id"]).shape[0] >= 0
    assert grown.diff_changed_rows(shrunk, key_columns=["id"]).shape[0] >= 0
    assert grown.diff_changed_cells(shrunk, key_columns=["id"]).shape[0] >= 0
    assert grown.diff_changed_cells(shrunk).shape[0] >= 0
    assert grown.different_rows(shrunk)["changed_count"] >= 0

    with pytest.raises(KeyError):
        grown.compare_frames(shrunk, key_columns=["missing"])

    with pytest.raises(IndexError):
        grown.diff_cells(old=999, new=-1)
    with pytest.raises(IndexError):
        grown.get_version(999)
    with pytest.raises(KeyError):
        grown.get_version("missing")
    assert grown.diff_summary()["added_rows"] >= 0


def test_queryset_and_grouped_branches(monkeypatch):
    table = DataTable(
        {
            "id": [1, 2, 3],
            "name": ["alice", "bob", "carol"],
            "age": [19, 24, 31],
            "active": [True, False, True],
        }
    )
    qs = dtpkg._ORMQuerySet(table)

    assert isinstance(qs["name"], dtpkg.pd.Series)
    assert isinstance(qs[["name"]], DataTable)
    assert qs.all().count() == 3
    assert qs.none().count() == 0
    assert qs.filter().count() == 3
    assert qs.filter("age > 20").count() == 2
    assert qs.exclude().count() == 0
    assert qs.exclude("age > 20").count() == 1
    assert qs.order_by().count() == 3
    assert qs.order_by("age").first()["age"].iloc[0] == 19
    assert qs.order_by("?").count() == 3
    assert qs.distinct().count() == 3
    assert qs.distinct("name").to_df().columns.tolist() == ["name"]
    assert qs.only().count() == 3
    assert qs.exclude_fields().count() == 3
    assert qs.values("name").first() == {"name": "alice"}
    assert qs.values_list("name", flat=True).first() == "alice"
    with pytest.raises(ValueError):
        qs.values_list("name", "age", flat=True)
    assert qs.get(name="alice")["age"].iloc[0] == 19
    with pytest.raises(KeyError):
        qs.get(name="missing")
    with pytest.raises(ValueError):
        qs.get(active=True)
    assert qs.count() == 3
    assert qs.exists() is True
    assert qs.paginate(limit=2).count() == 2
    with pytest.raises(ValueError):
        qs.paginate(limit=-1)
    with pytest.raises(ValueError):
        qs.group_by()
    assert qs.aggregate() == {}
    assert qs.sum("age") == {"age": 74}
    assert qs.avg("age") == {"age": 24.666666666666668}
    assert qs.min("age") == {"age": 19}
    assert qs.max("age") == {"age": 31}
    assert qs.aggregate(total=("age", "sum")) == {"total": 74}
    with pytest.raises(KeyError):
        qs.sum("missing")
    with pytest.raises(KeyError):
        qs.aggregate(total=("missing", "sum"))

    assert qs.to_markdown_table().startswith("|")
    assert "<table" in qs.to_html_table()
    assert "alice" in qs.to_csv_table()
    assert qs.render_table("markdown").startswith("|")
    assert "<table" in qs.render_table("html")
    assert "alice" in qs.render_table("csv")

    # rich / excel branches are exercised with direct helpers in the next test.
    joined = qs.join(DataTable({"id": [1, 2, 3], "city": ["hn", "sg", "dn"]}), on="id")
    assert "city" in joined.to_df().columns
    assert "city" in qs.left_join(DataTable({"id": [1], "city": ["hn"]}), on="id").to_df().columns
    assert "city" in qs.inner_join(DataTable({"id": [1], "city": ["hn"]}), on="id").to_df().columns
    assert qs.anti_join(DataTable({"id": [1], "city": ["hn"]}), on="id").count() == 2
    assert qs.ensure_columns("missing").to_df().columns.tolist()[-1] == "missing"
    assert qs.coerce_types({"age": "int64"}).to_df()["age"].dtype.kind in {"i", "u"}
    assert qs.validate_schema({"name": {"nullable": False}}).equals(table)
    assert qs.auto_version("auto")._frame.is_auto_versioning() is True
    assert qs.disable_auto_version()._frame.is_auto_versioning() is False
    assert isinstance(qs.snapshot("snap"), dtpkg._ORMQuerySet)

    with pytest.raises(KeyError):
        qs.filter(missing=1)
    with pytest.raises(KeyError):
        qs.exclude(missing=1)
    assert qs.join(DataTable({"id": [1]}), on="id").count() == 1
    with pytest.raises(ValueError):
        qs.join(DataTable({"id": [1]}), on=[])
    with pytest.raises(ValueError):
        qs.join(DataTable({"id": [1]}), left_on="id")
    with pytest.raises(ValueError):
        qs.join(DataTable({"id": [1]}), left_on=["id"], right_on=["id", "x"])
    assert qs.ensure_columns().count() == 3
    with pytest.raises(KeyError):
        qs.coerce_types({"missing": "int64"})
    with pytest.raises(ValueError):
        qs.validate_schema({"name": {"choices": ["zzz"]}})

    group = qs.group_by("active")
    assert group.count().to_df().columns.tolist() == ["active", "count"]
    assert group.annotate().to_df().columns.tolist() == ["active", "count"]
    assert group.annotate(total=("age", "sum")).to_df().columns.tolist() == ["active", "total"]
    assert group.annotate(age="sum").to_df().columns.tolist() == ["active", "age"]
    with pytest.raises(KeyError):
        group.annotate(total=("missing", "sum"))
    with pytest.raises(TypeError):
        group.aggregate(age=lambda s: s.max())
    with pytest.raises(TypeError):
        group.aggregate(age=123)
    assert group.sum("age")["age"]
    assert group.avg("age")["age"]
    assert group.min("age")["age"]
    assert group.max("age")["age"]
    assert group.aggregate(age="sum")["age"]
    with pytest.raises(KeyError):
        group.sum("missing")
    with pytest.raises(KeyError):
        group.aggregate(total=("missing", "sum"))

    # direct queryset delegations to diff helpers
    assert qs.diff_columns_against(table)["column_order_changed"] is False
    assert qs.diff_added_rows(table).shape[0] == 0
    assert qs.diff_removed_rows(table).shape[0] == 0
    assert qs.diff_unchanged_rows(table).shape[0] == 3
    assert qs.diff_changed_rows(table).shape[0] == 0
    assert qs.diff_changed_cells(table).shape[0] == 0
    assert qs.diff_report(table)["has_changes"] is False


def test_render_excel_and_rich_branches(monkeypatch):
    table = DataTable({"name": ["alice", None], "age": [19, 24]})

    assert table.to_markdown_table().startswith("|")
    assert "<table" in table.to_html_table()
    assert "alice" in table.to_csv_table()
    assert "alice" in table.render_table("markdown")
    assert "<table" in table.render_table("html")
    assert "alice" in table.render_table("csv")

    rich_table = table.to_rich_table(title="demo", show_index=True)
    assert rich_table is not None
    assert table.render_table("rich") is not None

    fake_openpyxl = types.ModuleType("openpyxl")
    fake_styles = types.ModuleType("openpyxl.styles")

    class FakeFont:
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

    class FakePatternFill:
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

    class FakeCell:
        def __init__(self, value, column_letter):
            self.value = value
            self.column_letter = column_letter
            self.font = None
            self.fill = None

    class FakeWorksheet:
        def __init__(self):
            self.freeze_panes = None
            self._columns = [
                (FakeCell("name", "A"), FakeCell("alice", "A")),
                (FakeCell("age", "B"), FakeCell(19, "B")),
            ]
            self.column_dimensions = {"A": types.SimpleNamespace(width=None), "B": types.SimpleNamespace(width=None)}

        @property
        def columns(self):
            return self._columns

        def __getitem__(self, item):
            if item == 1:
                return [column[0] for column in self._columns]
            raise KeyError(item)

    class FakeWorkbook:
        def __init__(self):
            self.worksheet = FakeWorksheet()

        def __getitem__(self, key):
            return self.worksheet

        def save(self, path):
            Path(path).write_text("fake workbook", encoding="utf-8")

    def fake_load_workbook(path):
        return FakeWorkbook()

    fake_openpyxl.load_workbook = fake_load_workbook
    fake_styles.Font = FakeFont
    fake_styles.PatternFill = FakePatternFill
    monkeypatch.setitem(sys.modules, "openpyxl", fake_openpyxl)
    monkeypatch.setitem(sys.modules, "openpyxl.styles", fake_styles)

    def fake_to_excel(self, output, sheet_name="Sheet1", index=False, **kwargs):
        Path(output).write_text("excel", encoding="utf-8")

    monkeypatch.setattr(DataTable, "to_excel", fake_to_excel, raising=False)

    excel_path = Path("tests") / "_datatable_coverage.xlsx"
    try:
        output = table.to_excel_table(excel_path, style_header=True, autofit=True, freeze_header=True)
        assert output == excel_path
        assert excel_path.exists()
        assert table.render_table("excel", path=excel_path) == excel_path
    finally:
        if excel_path.exists():
            excel_path.unlink()


def test_testcase_mixin_branches(monkeypatch):
    class Sample(DataTable):
        pass

    sample = Sample({"x": [1]})
    Sample.setUpClass()
    Sample.addClassCleanup(lambda: None)

    entered = []

    class CM:
        def __enter__(self):
            entered.append("enter")
            return "entered"

        def __exit__(self, exc_type, exc, tb):
            entered.append("exit")

    assert Sample.enterClassContext(CM()) == "entered"
    with pytest.raises(RuntimeError):
        Sample.addClassCleanup(lambda: (_ for _ in ()).throw(RuntimeError("cleanup")))
        Sample.doClassCleanups()
    Sample.tearDownClass()
    assert entered == ["enter", "exit"]
    sample.setUp()
    sample.tearDown()
    sample.assertIsSubclass(Sample, DataTable)
    sample.assertNotIsSubclass(int, DataTable)
    with pytest.raises(AssertionError):
        sample.assertIsSubclass(int, DataTable)
    with pytest.raises(AssertionError):
        sample.assertNotIsSubclass(Sample, DataTable)
    with pytest.raises(AssertionError):
        sample.assertIsSubclass(123, DataTable)
    with pytest.raises(AssertionError):
        sample.assertNotIsSubclass(123, DataTable)

    monkeypatch.setattr(sample, "_testcase", lambda: types.SimpleNamespace())
    with sample.assertNoLogs("soigia.edge", level=logging.WARNING):
        pass
