"""Inline choice toggles: a decision answered in one tap on the row, instead
of a button that hides a select nobody can see until they open it."""

import pytest

from app.db import get_session_factory
from app.schemas import SpecError, validate_spec
from app.services import items as eng
from app.services import punchlists as pls
from app.services import workflows as wf
from tests.conftest import OWNER, as_user


@pytest.fixture
def session():
    with get_session_factory()() as s:
        yield s
        s.commit()


def _spec(**a):
    step = {"id": "a", "owner": "client",
            "choices": [{"key": "yes", "label": "Yes, add it", "to": "b"},
                        {"key": "no", "label": "No, skip it", "to": "b"}]}
    step.update(a)
    return {"key": "t_choice", "title": "Decide", "start": "a", "steps": [
        step,
        {"id": "b", "owner": "team", "instruction": "They said {{fields.a.choice}}.",
         "primary": {"label": "Noted", "to": "$done"}}]}


def test_a_step_has_a_button_or_choices_never_both_never_neither():
    validate_spec(_spec())
    for bad, needle in [
        ({"primary": {"label": "Go", "to": "b"}}, "EITHER"),
        ({"choices": []}, "EITHER"),
        ({"choices": [{"key": "y", "label": "Y", "to": "b"}]}, "at least two"),
        ({"fields": [{"key": "f", "type": "text", "label": "F"}]}, "ONE tap"),
    ]:
        with pytest.raises(SpecError) as e:
            validate_spec(_spec(**bad))
        assert any(needle in m for m in e.value.errors), e.value.errors


def test_choice_keys_cannot_shadow_engine_actions_or_alternatives():
    with pytest.raises(SpecError) as e:
        validate_spec(_spec(choices=[{"key": "primary", "label": "P", "to": "b"},
                                     {"key": "n", "label": "N", "to": "b"}]))
    assert any("reserved" in m for m in e.value.errors)

    with pytest.raises(SpecError) as e:
        validate_spec(_spec(
            choices=[{"key": "later", "label": "L", "to": "b"},
                     {"key": "n", "label": "N", "to": "b"}],
            alternatives=[{"key": "later", "label": "Later", "kind": "flag", "tone": "ack"}]))
    assert any("both a choice and an alternative" in m for m in e.value.errors)


def test_choosing_advances_and_the_label_is_readable_downstream(session, kit):
    pl = pls.create(session, OWNER, "Choice world")
    wf.publish(session, OWNER, _spec())
    session.commit()
    item = eng.instantiate(session, OWNER, pl.id, template_key="t_choice")
    session.commit()

    r = eng.render_item(session, item, for_team=True)
    assert [c["label"] for c in r["step"]["choices"]] == ["Yes, add it", "No, skip it"]
    assert r["step"]["primary_label"] == ""          # nothing to mislabel

    eng.act(session, item, actor="cleo", actor_kind="client", action="no")
    session.commit()
    r = eng.render_item(session, item, for_team=True)
    # one tap moved it on, and the team's step can SAY what was chosen
    assert r["step"]["id"] == "b"
    assert r["step"]["instruction"] == "They said No, skip it."


def test_a_choice_can_branch_to_different_steps(session, kit):
    spec = _spec(choices=[{"key": "ours", "label": "Host with us", "to": "b"},
                          {"key": "own", "label": "Our own host", "to": "which"}])
    spec["steps"].append({"id": "which", "owner": "client",
                          "fields": [{"key": "host", "type": "text", "label": "Which host?"}],
                          "primary": {"label": "Sent", "to": "b"}})
    pl = pls.create(session, OWNER, "Branch world")
    wf.publish(session, OWNER, spec)
    session.commit()
    item = eng.instantiate(session, OWNER, pl.id, template_key="t_choice")
    session.commit()
    eng.act(session, item, actor="cleo", actor_kind="client", action="own")
    session.commit()
    assert eng.render_item(session, item, for_team=True)["step"]["id"] == "which"


def test_unknown_and_stale_actions_are_refused(session, kit):
    pl = pls.create(session, OWNER, "Refuse world")
    wf.publish(session, OWNER, _spec())
    session.commit()
    item = eng.instantiate(session, OWNER, pl.id, template_key="t_choice")
    session.commit()
    with pytest.raises(eng.ItemError) as e:
        eng.act(session, item, actor="cleo", actor_kind="client", action="maybe")
    assert e.value.code == "NO_SUCH_ACTION"
    # "primary" is not a way to skip past a choice
    with pytest.raises(eng.ItemError) as e:
        eng.act(session, item, actor="cleo", actor_kind="client", action="primary")
    assert e.value.code == "NO_SUCH_ACTION"


def test_the_shipped_age_verification_item_answers_in_one_tap(client, kit):
    as_user(client, OWNER)
    pid = client.post("/api/punchlists", json={"title": "Ship check"}).json()["id"]
    r = client.post(f"/api/punchlists/{pid}/items",
                    json={"template_key": "decide_age_verification"})
    assert r.status_code == 201, r.text
    step = r.json()["item"]["step"]
    assert [c["label"] for c in step["choices"]] == ["Yes", "No"]
    assert step["fields"] == [] and step["primary_label"] == ""
    iid = r.json()["id"]
    a = client.post(f"/api/items/{iid}/actions", json={"action": "add_it"})
    assert a.status_code == 200, a.text
    assert "Yes" in a.json()["item"]["step"]["instruction"]
    # and the client can still see what they answered
    assert a.json()["item"]["choice_made"]["label"] == "Yes"


def test_the_answer_stays_visible_after_the_step_moves_on(session, kit):
    """A form that saves as you go shows you what you saved — the client must
    still see 'Yes' after the item has passed to the team."""
    pl = pls.create(session, OWNER, "Persist world")
    wf.publish(session, OWNER, _spec())
    session.commit()
    item = eng.instantiate(session, OWNER, pl.id, template_key="t_choice")
    session.commit()
    assert eng.render_item(session, item, for_team=False)["choice_made"] is None

    eng.act(session, item, actor="cleo", actor_kind="client", action="yes")
    session.commit()

    for as_team in (False, True):
        made = eng.render_item(session, item, for_team=as_team)["choice_made"]
        assert made["key"] == "yes" and made["label"] == "Yes, add it"
        assert made["step"] == "a"
        # the whole option set rides along, so the control can render settled
        # even though the step it belonged to is no longer current
        assert [o["key"] for o in made["options"]] == ["yes", "no"]

    # and it survives the item finishing
    eng.act(session, item, actor=OWNER, actor_kind="team", action="primary")
    session.commit()
    r = eng.render_item(session, item, for_team=False)
    assert r["client_state"] == "done"
    assert r["choice_made"]["label"] == "Yes, add it"
