"""The effective read (spec §3.3): the newest decision per `(entity_type, entity_id, field)`
by `(decided_at, id)`, one ordered query, dialect-neutral (a `DISTINCT ON` compiles to a plain
`DISTINCT` on SQLite and inverts the order, so the dict overwrite does the work), a `release`
row emptying the field. Every reader calls this, so none can forget the release rule.

Sources of truth: this module, `tests/test_effective.py`, `keying.load_maps` (the key path's
one load), the spec §3.
"""

from __future__ import annotations

from collections.abc import Iterable

from sqlalchemy import inspect, select
from sqlalchemy.orm import Session

from app.models import Decision, ProductVariant, Suggestion


def table_present(db: Session, name: str = "decisions") -> bool:
    """Whether the ledger exists on this database: a pure-logic suite that builds only the
    catalogue tables must not fail on a reader in the merge path."""
    return name in inspect(db.connection()).get_table_names()


def effective(db: Session, entity_type: str, entity_ids: Iterable[int] | None = None,
              fields: Iterable[str] | None = None) -> dict[tuple[int, str], Decision]:
    """The effective decision per `(entity_id, field)`; an effective `release` is absent."""
    if not table_present(db):
        return {}
    stmt = select(Decision).where(Decision.entity_type == entity_type)
    if entity_ids is not None:
        ids = [int(i) for i in entity_ids]
        if not ids:
            return {}
        stmt = stmt.where(Decision.entity_id.in_(ids))
    if fields is not None:
        stmt = stmt.where(Decision.field.in_(list(fields)))
    stmt = stmt.order_by(Decision.entity_id, Decision.field, Decision.decided_at.asc(), Decision.id.asc())
    out: dict[tuple[int, str], Decision] = {}
    for row in db.scalars(stmt):
        key = (row.entity_id, row.field)
        if row.effect == "release":
            out.pop(key, None)
        else:
            out[key] = row
    return out


def merged_into_map(db: Session) -> dict[int, int]:
    """`{id: merged_into_id}` for every merged-away variant, loaded in one query."""
    return {i: t for i, t in db.execute(select(ProductVariant.id, ProductVariant.merged_into_id)
                                        .where(ProductVariant.merged_into_id.isnot(None)))}


def chase(merged: dict[int, int], variant_id: int, limit: int = 8) -> int:
    seen = 0
    while variant_id in merged and seen < limit:
        variant_id, seen = merged[variant_id], seen + 1
    return variant_id


def separated(db: Session, merged: dict[int, int] | None = None) -> set[frozenset[int]]:
    """Every variant pair a person kept separate, each side chased through `merged_into_id` to
    its live survivor (spec §3.1), so a veto follows a side that merged elsewhere."""
    if not table_present(db, "suggestions"):
        return set()
    merged = merged_into_map(db) if merged is None else merged
    out: set[frozenset[int]] = set()
    for left, right in db.execute(select(Suggestion.left_id, Suggestion.right_id)
                                  .where(Suggestion.level == "product", Suggestion.decision == "separate")):
        if left is None or right is None:
            continue
        a, b = chase(merged, left), chase(merged, right)
        if a != b:
            out.add(frozenset((a, b)))
    return out
