"""How much of a person's judgement a suggestion needs, which is not how sure the machine is.

Sources of truth: this module, `models/decisions.py` (`ATTENTION_LEVELS`), `proposals.py` (the
sheet reads it), `docs/AI-REVIEW-GUIDELINES.md`, `tests/test_attention.py`.

Confidence measures the inference: how surely these words mean that value. Attention measures the
consequence: how much a person's judgement is worth spending here. They are different axes and
sorting by the wrong one buries the rows that matter. Rian, 18 Sep, naming the five:

    critical  the answer sets a precedent or makes a new kind of thing. The FIRST decision about a
              gift set is critical, because every later set is answered by it.
    high      a significant change, roughly as arguable either way, or with a large downstream
              effect such as overwriting attributes across many variants.
    medium    the proposal is probably right; a person does not really need to check it.
    low       there is barely a decision, but a person might catch what a pass missed.
    none      nothing to decide. The row exists so it can be marked seen.

**Critical is relative to what has already been answered.** The first set decision sets the
precedent; the tenth follows it. So a pass may propose `critical`, and this downgrades it to `high`
once a decision of that kind exists, which is what keeps the top of the list short as rian works
through it. Nothing upgrades on its own: a pass that said `critical` and a precedent that does not
exist yet stay critical.
"""

from __future__ import annotations

from typing import Any

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models import Decision, Proposal
from app.models.decisions import ATTENTION_LEVELS

LEVELS = ATTENTION_LEVELS
#: What a person reads for each, and when it applies. Shown on the guide and beside the filter.
LEVEL_WHAT: dict[str, str] = {
    "critical": "sets a precedent, or makes a new kind of thing. Answer these first: every later "
                "question of the same kind is answered by what you decide here.",
    "high": "a real change, about as arguable either way, or one that rewrites a lot downstream.",
    "medium": "probably right. You do not need to check it, but you can.",
    "low": "barely a decision. Worth an eye in case the pass missed something.",
    "none": "nothing to decide. Listed so it can be marked seen.",
}


def precedent_key(row: Proposal) -> str:
    """The KIND of judgement a row is, which is what a precedent is set on.

    Two questions share a key when answering one should answer the other. A gift set folding into
    its bottle's line and another gift set doing the same are one kind; a gift set and a refill are
    not, because rian drew a distinction between them and each sets its own precedent.
    """
    if row.entity_type == "suggestion" and row.field == "decision":
        level = row.natural_key.split(":")[1] if row.natural_key.startswith("pair:") else "pair"
        return f"same-or-separate:{level}"
    if row.field.startswith("attribute:"):
        return f"attribute:{row.field[len('attribute:'):]}"
    if row.field == "product_line":
        # The word a set-like membership turns on. Each is its own precedent: rian ruled that a
        # refill serving one line and a coffret spanning four are not the same case.
        reason = (row.reason or "").lower()
        for word in ("coffret", "gift set", "gift", "refill", "duo", "trio", "kit", "set", "pack"):
            if word in reason:
                return f"membership:{word.replace(' ', '-')}"
        return "membership:plain"
    return f"{row.entity_type}:{row.field}"


#: A registered kind leaves `high` for the confidence-derived level after this many consistent
#: decisions under it (an overturn or a "something else" on a lead resets the count). Mirrored in
#: `docs/REVIEW-PROCESS.md` section 4; `tests/test_precedents.py` holds the two equal.
LEAVE_HIGH_AFTER = 3
_RANK = {level: i for i, level in enumerate(LEVELS)}  # critical = 0 ... none = 4


def _precedents(db: Session) -> set[str]:
    """Legacy: the kinds answered under rows that named no slug (files before process version 6,
    rule and arrival rows), keyed on `precedent_key`. The register replaces this for any row that
    names its precedent."""
    out: set[str] = set()
    for row in db.scalars(select(Proposal).where(Proposal.status.in_(("approved", "rejected", "deferred")))):
        out.add(precedent_key(row))
    return out


def _from_confidence(row: Proposal) -> str:
    confidence = float(row.confidence) if row.confidence is not None else None
    if confidence is None or confidence < 0.7:
        return "high"
    if confidence >= 0.9:
        return "low"
    return "medium"


def _is_merge(row: Proposal) -> bool:
    return row.field == "merged_into" or (row.entity_type == "suggestion" and row.natural_key.startswith("pair:product:"))


def _derive(row: Proposal, has_precedent: bool) -> str:
    """The level for a legacy row (no slug) whose pass did not set one. Deliberately cautious:
    where a rule cannot tell, it asks for more of a person rather than less."""
    if not has_precedent:
        return "critical"
    if _is_merge(row):
        return "high"  # variants become one, which changes what a price is compared against
    return _from_confidence(row)


def floor_for(row: Proposal, register: dict) -> tuple[str, str]:
    """The mechanical floor for a row that names its precedent, and why (K12.4; the plan's B2).
    The pass's level may be higher than this, never lower.

    * a slug the register does not hold: `critical`, by construction;
    * a follower (`follows` set) whose lead is in this file: `none`, it goes with the lead;
    * registered, but the row is unlike every case decided under the slug: `high`, naming what;
    * registered with fewer than LEAVE_HIGH_AFTER consistent decisions: `high`;
    * an overturned precedent: `high` (the next answers re-set it);
    * a merge or a product pair below the streak: `high`; then the confidence-derived level."""
    slug = (row.precedent or "").strip()
    known = register.get(slug)
    if known is None:
        return "critical", "no precedent is registered for this kind of question; the answer sets one"
    if row.follows:
        return "none", f"follows {row.follows}"
    if row.unlike:
        what = "; ".join(f"{k}: {', '.join(v)}" for k, v in row.unlike.items())
        return "high", f"unlike every case decided under {slug} ({what})"
    if known.status == "overturned":
        return "high", f"{slug} was overturned; the next answers set it again"
    streak = int(known.streak or 0)
    if streak < LEAVE_HIGH_AFTER:
        return "high", f"{slug} has {streak} consistent decision(s); {LEAVE_HIGH_AFTER} let it leave high"
    if _is_merge(row) and streak < LEAVE_HIGH_AFTER:
        return "high", "a merge changes what a price is compared against"
    return _from_confidence(row), f"{slug} settled by {streak} decisions"


def for_rows(db: Session, rows: list[Proposal]) -> dict[int, str]:
    """The level for each row, by proposal id: the higher of the pass's level and the register's
    floor. One pass over the ledger, not one per row."""
    return {pid: level for pid, (level, _why) in explain(db, rows).items()}


def explain(db: Session, rows: list[Proposal]) -> dict[int, tuple[str, str]]:
    """`for_rows` with the reason beside each level, for the sheet and the card."""
    if not rows:
        return {}
    from app.services import precedents

    register = precedents.register(db)
    known: set[str] | None = None
    out: dict[int, tuple[str, str]] = {}
    for row in rows:
        asked = row.attention if row.attention in LEVELS else None
        if (row.precedent or "").strip():
            floor, why = floor_for(row, register)
            if asked is not None and _RANK[asked] < _RANK[floor]:
                out[row.id] = (asked, f"the pass rated it {asked}")
            else:
                out[row.id] = (floor, why)
            continue
        if known is None:
            known = _precedents(db)
        settled = precedent_key(row) in known
        if asked is None:
            out[row.id] = (_derive(row, settled), "derived: no precedent named")
        elif asked == "critical" and settled:
            out[row.id] = ("high", "the precedent exists, so this is no longer the question that sets it")
        else:
            out[row.id] = (asked, f"the pass rated it {asked}")
    return out


def summary(levels: dict[int, str]) -> dict[str, int]:
    """How many rows sit at each level, highest first, for the sheet's own heading."""
    counts = {level: 0 for level in LEVELS}
    for level in levels.values():
        if level in counts:
            counts[level] += 1
    return counts


def describe() -> list[dict[str, Any]]:
    """The levels as the review guide shows them."""
    return [{"level": level, "what": LEVEL_WHAT[level]} for level in LEVELS]
