"""Two options plus toggleable variants (rian's M2 ask 3).

The load-bearing rule: a variant choice is PART OF the locked direction. Adi's
closing note asks the client for "an opening AND a brand level" — two decisions
— so a selection that recorded only the option would be half an answer, and the
rollup must not call that `done`.
"""

from tests.conftest import as_user
from tests.test_domain import add_screen_option, make_project


def _variant(client, oid, axis="brand", key="quiet", label="Quiet brand",
             css="v-brand-quiet", axis_label="Brand", default=False):
    r = client.post(f"/api/options/{oid}/variants", json={
        "axis": axis, "key": key, "label": label, "css_class": css,
        "axis_label": axis_label, "is_default": default})
    assert r.status_code == 200, r.text
    return r.json()["id"]


def _brand_axis(client, oid):
    _variant(client, oid, key="quiet", label="Quiet brand",
             css="v-brand-quiet", default=True)
    _variant(client, oid, key="hive", label="Full hive", css="v-brand-hive")


def test_variants_ride_along_with_the_option(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _brand_axis(client, oid)

    as_user(client, "cleo")
    option = client.get(f"/api/projects/{iid}").json()["screens"][0]["options"][0]
    assert [v["key"] for v in option["variants"]] == ["quiet", "hive"]
    assert [v["css_class"] for v in option["variants"]] == \
        ["v-brand-quiet", "v-brand-hive"]
    assert option["variants"][0]["axis_label"] == "Brand"


def test_choosing_records_the_variant_too(client, kit):
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    _brand_axis(client, oid)
    client.post(f"/api/projects/{iid}/send")

    as_user(client, "cleo")
    r = client.post(f"/api/screens/{sid}/select",
                    json={"option_id": oid, "variants": {"brand": "hive"}})
    assert r.status_code == 200, r.text
    screen = client.get(f"/api/projects/{iid}").json()["screens"][0]
    assert screen["selected_option_id"] == oid
    assert screen["selected_variants"] == {"brand": "hive"}
    assert client.get(f"/api/projects/{iid}/status").json()["state"] == "done"


def test_an_untouched_toggle_still_records_a_complete_choice(client, kit):
    """A client who never touches a toggle is choosing what they were LOOKING
    at, so the default fills in rather than leaving the direction half-locked."""
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    _brand_axis(client, oid)
    client.post(f"/api/projects/{iid}/send")

    as_user(client, "cleo")
    client.post(f"/api/screens/{sid}/select", json={"option_id": oid})
    screen = client.get(f"/api/projects/{iid}").json()["screens"][0]
    assert screen["selected_variants"] == {"brand": "quiet"}   # the default
    assert client.get(f"/api/projects/{iid}/status").json()["state"] == "done"


def test_a_variant_that_was_never_offered_is_refused(client, kit):
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    _brand_axis(client, oid)
    as_user(client, "cleo")
    r = client.post(f"/api/screens/{sid}/select",
                    json={"option_id": oid, "variants": {"brand": "neon"}})
    assert r.status_code == 400
    assert r.json()["detail"]["error_code"] == "BAD_VARIANT"


def test_a_second_axis_must_also_be_answered_for_done(client, kit):
    """Two axes, one answered: the direction is not locked. This is the whole
    reason variants live in the rollup rather than beside it."""
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    _brand_axis(client, oid)
    _variant(client, oid, axis="header", key="a", label="Header A",
             css="v-header-a", axis_label="Header", default=True)
    _variant(client, oid, axis="header", key="b", label="Header B",
             css="v-header-b")
    client.post(f"/api/projects/{iid}/send")

    as_user(client, "cleo")
    client.post(f"/api/screens/{sid}/select",
                json={"option_id": oid, "variants": {"brand": "hive"}})
    screen = client.get(f"/api/projects/{iid}").json()["screens"][0]
    # The unanswered axis filled from its default rather than being left blank.
    assert screen["selected_variants"] == {"brand": "hive", "header": "a"}
    assert client.get(f"/api/projects/{iid}/status").json()["state"] == "done"


def test_unpicking_clears_the_variants_too(client, kit):
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    _brand_axis(client, oid)
    client.post(f"/api/projects/{iid}/send")
    as_user(client, "cleo")
    client.post(f"/api/screens/{sid}/select",
                json={"option_id": oid, "variants": {"brand": "hive"}})
    client.post(f"/api/screens/{sid}/select", json={"option_id": None})
    screen = client.get(f"/api/projects/{iid}").json()["screens"][0]
    assert screen["selected_option_id"] is None
    assert screen["selected_variants"] is None


def test_the_team_is_told_WHICH_variant_was_picked(client, kit):
    """"Direction picked: The Direct Answer" is half the decision when the
    option carries axes — and the half it drops is the one we would get wrong."""
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    _brand_axis(client, oid)
    client.post(f"/api/projects/{iid}/send")

    as_user(client, "cleo")
    client.post(f"/api/screens/{sid}/select",
                json={"option_id": oid, "variants": {"brand": "hive"}})
    as_user(client, "mara")
    bodies = [n["body"] for n in client.get("/api/notifications").json()["notifications"]]
    assert any("Full hive" in b for b in bodies), bodies


def test_authoring_a_variant_is_manager_only(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    vid = _variant(client, oid)
    as_user(client, "cleo")
    r = client.post(f"/api/options/{oid}/variants", json={
        "axis": "brand", "key": "neon", "label": "Neon", "css_class": "v-neon"})
    assert r.status_code == 403
    assert client.delete(f"/api/variants/{vid}").status_code == 403


def test_a_duplicate_choice_on_an_axis_is_refused(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _variant(client, oid)
    r = client.post(f"/api/options/{oid}/variants", json={
        "axis": "brand", "key": "quiet", "label": "Quiet again",
        "css_class": "v-other"})
    assert r.status_code == 400
    assert r.json()["detail"]["error_code"] == "EXISTS"


def test_options_without_variants_are_unaffected(client, kit):
    """The overwhelming majority of options carry no variants; they must lock a
    direction exactly as before."""
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    client.post(f"/api/projects/{iid}/send")
    as_user(client, "cleo")
    client.post(f"/api/screens/{sid}/select", json={"option_id": oid})
    screen = client.get(f"/api/projects/{iid}").json()["screens"][0]
    assert screen["selected_variants"] is None
    assert client.get(f"/api/projects/{iid}/status").json()["state"] == "done"


# ------------------------------------------- the design declares its own looks

VARIANT_HTML = b"""<!doctype html><html><head><style>
  html.v-brand-hive { --accent: #c8781a; }
  html.v-brand-hive .logo::after { content: "*"; }
  html:not(.v-brand-hive) .comb { display: none; }
  :root.v-header-b .bar { flex-direction: column; }
  .card { border: 1px solid #eee; }        /* an ordinary class, not a variant */
  html body { margin: 0; }                 /* html, but no class at all */
</style></head><body><div class="card">hi</div></body></html>"""


def _with_design(client, oid, html=VARIANT_HTML):
    r = client.post(f"/api/options/{oid}/files",
                    files=[("files", ("index.html", html, "text/html"))])
    assert r.status_code == 200, r.text


def test_the_form_offers_the_classes_the_design_declares(client, kit):
    """A mistyped class fails silently — the frame simply does not change. So
    the author picks from what the bundle actually scopes to <html>."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _with_design(client, oid)

    r = client.get(f"/api/options/{oid}/variant-classes")
    assert r.status_code == 200, r.text
    body = r.json()
    # Both spellings of "scoped to the root" are found, in either order of use.
    assert body["classes"] == ["v-brand-hive", "v-header-b"]
    # An ordinary content class is not a variation, and `html body` has none.
    assert "card" not in body["classes"]


def test_classes_already_spoken_for_drop_out_of_the_suggestions(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _with_design(client, oid)
    _variant(client, oid, key="hive", label="Full hive", css="v-brand-hive")

    body = client.get(f"/api/options/{oid}/variant-classes").json()
    assert body["classes"] == ["v-brand-hive", "v-header-b"]
    assert body["unused"] == ["v-header-b"]      # the likely next choice


def test_only_the_team_reads_the_authoring_detail(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _with_design(client, oid)

    as_user(client, "cleo")
    assert client.get(f"/api/options/{oid}/variant-classes").status_code == 403


def test_shown_first_is_a_fact_about_the_axis(client, kit):
    """Two defaults on one axis would make the opening view depend on row
    order. Setting one clears the other."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _variant(client, oid, key="quiet", label="Quiet", css="v-brand-quiet",
             default=True)
    _variant(client, oid, key="hive", label="Full hive", css="v-brand-hive",
             default=True)
    # A second axis keeps its own default — the rule is per axis, not per option.
    _variant(client, oid, axis="header", axis_label="Header", key="b",
             label="Stacked", css="v-header-b", default=True)

    option = client.get(f"/api/projects/{iid}").json()["screens"][0]["options"][0]
    defaults = {v["key"]: v["is_default"] for v in option["variants"]}
    assert defaults == {"quiet": False, "hive": True, "b": True}


def test_the_design_as_it_ships_is_a_choice(client, kit):
    """A bundle written as `html:not(.v-brand-hive)` has NO class for its plain
    side. Forcing one would put a lie in the data — and without the plain
    choice the single alternative is permanently on, so the base is
    unreachable."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _with_design(client, oid)
    _variant(client, oid, key="plain", label="Plain", css="", default=True)
    _variant(client, oid, key="hive", label="Full hive", css="v-brand-hive")

    option = client.get(f"/api/projects/{iid}").json()["screens"][0]["options"][0]
    assert [(v["label"], v["css_class"]) for v in option["variants"]] == [
        ("Plain", ""), ("Full hive", "v-brand-hive")]


def test_one_base_choice_per_axis(client, kit):
    """Two "as it ships" choices on one axis are indistinguishable to a client
    — both render the same page under different names."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _variant(client, oid, key="plain", label="Plain", css="")

    r = client.post(f"/api/options/{oid}/variants", json={
        "axis": "brand", "key": "bare", "label": "Bare", "css_class": "",
        "axis_label": "Brand"})
    assert r.status_code == 400
    assert r.json()["detail"]["error_code"] == "EXISTS"
    # A different axis may have its own base choice.
    _variant(client, oid, axis="header", axis_label="Header", key="std",
             label="Standard", css="")


def test_a_variant_still_needs_a_name(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    r = client.post(f"/api/options/{oid}/variants", json={
        "axis": "brand", "key": "", "label": "", "css_class": "v-brand-hive"})
    assert r.status_code == 400
    assert r.json()["detail"]["error_code"] == "BAD_INPUT"


# ------------------------------------------- turning a design's looks into a toggle

def test_setup_builds_the_whole_toggle_in_one_act(client, kit):
    """The admin sends labels. The base choice, the keys, the order and the
    default are the server's job: they are the shape of the table, not of the
    job the admin came to do."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _with_design(client, oid)

    r = client.post(f"/api/options/{oid}/variants/setup", json={
        "axis": "brand", "axis_label": "Brand", "base_label": "Plain",
        "choices": [{"css_class": "v-brand-hive", "label": "Full hive"}]})
    assert r.status_code == 200, r.text

    option = client.get(f"/api/projects/{iid}").json()["screens"][0]["options"][0]
    assert [(v["label"], v["css_class"], v["is_default"]) for v in option["variants"]] == [
        ("Plain", "", True), ("Full hive", "v-brand-hive", False)]
    assert [v["axis_label"] for v in option["variants"]] == ["Brand", "Brand"]


def test_setup_refuses_to_build_a_toggle_with_nothing_to_toggle(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    r = client.post(f"/api/options/{oid}/variants/setup", json={"choices": []})
    assert r.status_code == 400
    assert r.json()["detail"]["error_code"] == "BAD_INPUT"


def test_setup_will_not_build_a_second_toggle_on_the_same_axis(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    body = {"axis": "brand", "axis_label": "Brand", "base_label": "Plain",
            "choices": [{"css_class": "v-brand-hive", "label": "Full hive"}]}
    assert client.post(f"/api/options/{oid}/variants/setup", json=body).status_code == 200
    r = client.post(f"/api/options/{oid}/variants/setup", json=body)
    assert r.status_code == 400
    assert r.json()["detail"]["error_code"] == "EXISTS"


def test_setup_is_the_team_side_only(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    as_user(client, "cleo")
    r = client.post(f"/api/options/{oid}/variants/setup", json={
        "choices": [{"css_class": "v-brand-hive", "label": "Full hive"}]})
    assert r.status_code == 403


def test_the_second_choice_is_told_what_it_is_missing(client, kit):
    """"Base" then a second blank choice is the mistake an admin actually
    makes. The refusal has to say what the second one needs, not restate the
    rule in the app's own vocabulary."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    _variant(client, oid, key="base", label="Base", css="", default=True)

    r = client.post(f"/api/options/{oid}/variants", json={
        "axis": "brand", "key": "hive", "label": "Hive", "css_class": "",
        "axis_label": "Brand"})
    assert r.status_code == 400
    detail = r.json()["detail"]
    assert detail["error_code"] == "EXISTS"
    assert "Base" in detail["summary"]
    assert "v-brand-hive" in detail["details"]
