"""The third answer: defer with a note, and the note reaching the next pass (K11.4).

What it cost. Before this a row could only be approved or rejected-with-a-sentence, and **nothing
ever read the sentence**. Rian's own worked example is a judgement he has NOT settled -- a refill
serving several product lines is not the same case as one line with one refill, and a gift set
spanning several lines is not the same as three shades of one line -- and it had nowhere to live
except "no", which is a different and wrong answer. Every pass therefore started from zero and
asked the set/coffret question identically forever.

So: a deferred row leaves the queue, is never applied, carries its note, and a later pass may not
re-propose that question until it names the note it read.
"""

from __future__ import annotations

import pytest
from sqlalchemy import select

from app.models import Decision, Proposal
from app.services import proposals
from app.services.decisions.writer import Refused
from tests.test_proposals_load import _counts, _file, db  # noqa: F401 (the fixture)

NOTE = ("a refill serving several product lines is not the same case as one line with one refill; "
        "ask again with which one this is, and cite the words you read it from")


def _first_open(db):
    return db.scalar(select(Proposal).where(Proposal.status == "open").order_by(Proposal.position, Proposal.id))


def _defer(db, row, note=NOTE):
    return proposals.approve(db, "chanel", "claude/2026-09-17/chanel-test", scope=None, by=1,
                             defer=[str(row.uid)], note=note)


def test_a_deferred_row_leaves_the_queue_and_is_never_applied(db):
    proposals.load(db, _file(db))
    row = _first_open(db)
    before = db.scalar(select(Decision).order_by(Decision.id.desc()))
    result = _defer(db, row)
    assert result["counts"]["deferred"] == 1 and result["counts"]["approved"] == 0
    db.refresh(row)
    assert row.status == "deferred" and row.resolution_note == NOTE
    assert row.decision_id is None, "a defer is not a decision"
    assert db.scalar(select(Decision).order_by(Decision.id.desc())) is before, "nothing was written to the ledger"
    # ...and it is off the sheet and out of the index's open count.
    uids = [r["uid"] for g in proposals.sheet(db, "chanel")["groups"] for r in g["rows"] if r["status"] == "open"]
    assert str(row.uid) not in uids


def test_a_defer_without_a_note_is_refused(db):
    """The note is the whole point: a defer without one is indistinguishable from silence."""
    proposals.load(db, _file(db))
    row = _first_open(db)
    with pytest.raises(Refused) as exc:
        _defer(db, row, note="  ")
    assert exc.value.code == "VALUE_INVALID" and "next pass reads" in exc.value.summary


def test_a_row_is_rejected_or_deferred_but_never_both(db):
    proposals.load(db, _file(db))
    row = _first_open(db)
    with pytest.raises(Refused) as exc:
        proposals.approve(db, "chanel", "claude/2026-09-17/chanel-test", scope=None, by=1,
                          reject=[str(row.uid)], defer=[str(row.uid)], note=NOTE)
    assert exc.value.code == "VALUE_INVALID"


def test_the_note_is_what_the_next_pass_reads(db):
    proposals.load(db, _file(db))
    row = _first_open(db)
    key, field = row.natural_key, row.field
    _defer(db, row)
    out = proposals.deferred_notes(db, "chanel")
    assert out["deferred"] == 1 and out["total"] == 1
    note = out["notes"][0]
    assert note["note"] == NOTE and note["status"] == "deferred" and note["uid"] == str(row.uid)
    assert note["natural_key"] == key and note["field"] == field and note["by"] == "rian"
    # A pass narrows to the question in front of it.
    assert proposals.deferred_notes(db, "chanel", field=field, natural_key=key)["total"] == 1
    assert proposals.deferred_notes(db, "chanel", field="no_such_field")["total"] == 0
    # And with no brand at all, a pass reads every note there is.
    assert proposals.deferred_notes(db)["total"] == 1


def test_a_second_pass_may_not_re_propose_the_question_without_naming_the_note(db):
    """The acceptance criterion: a deferred row with a note changes what the next pass proposes."""
    proposals.load(db, _file(db))
    row = _first_open(db)
    key, field, uid = row.natural_key, row.field, str(row.uid)
    _defer(db, row)

    second = _file(db, name="claude/2026-09-18/chanel-2")
    with pytest.raises(Refused) as exc:
        proposals.load(db, second)
    assert exc.value.code == "DEFERRED_NOT_READ"
    assert "refill" in exc.value.summary and uid in exc.value.summary, exc.value.summary

    # Having read it, the pass names the note and the same file loads.
    for item in second["proposals"]:
        if item["entity"]["key"] == key and item["field"] == field:
            item["answers_deferred"] = uid
    counts = proposals.load(db, second)
    assert counts["rows"] == len(second["proposals"])


def test_a_pass_that_does_not_touch_the_deferred_question_is_unaffected(db):
    """Only the same question is held back: a pass proposing something else loads untouched."""
    proposals.load(db, _file(db))
    row = _first_open(db)
    _defer(db, row)
    other = _file(db, name="claude/2026-09-18/chanel-3")
    other["proposals"] = [p for p in other["proposals"]
                          if not (p["entity"]["key"] == row.natural_key and p["field"] == row.field)]
    assert proposals.load(db, other)["rows"] == len(other["proposals"])


def test_a_rejection_sentence_is_reasoning_too(db):
    """A pair a person kept separate is never proposed as one again, so its note rides along."""
    proposals.load(db, _file(db))
    row = _first_open(db)
    proposals.approve(db, "chanel", "claude/2026-09-17/chanel-test", scope=None, by=1,
                      reject=[str(row.uid)], note="two different scents, not one line")
    out = proposals.deferred_notes(db, "chanel")
    assert out["total"] == 1 and out["deferred"] == 0
    assert out["notes"][0]["status"] == "rejected"
