"""Paste written notes, get walkthrough beats.

The walkthrough is written as prose long before easel sees it — Adi's designer
notes carry a dozen-plus beats as title + one sentence + the element they point
at. Typing those back one drag at a time is retyping a document that exists.
"""

import pytest

from app.services import walkthrough_import as imp
from tests.conftest import as_user
from tests.test_domain import add_screen_option, make_project

NOTES = """Start here 👋
Follow the numbered notes top to bottom.

Your number, in your face
@.phone
Front and centre on every page, real size, tap-to-call.

!Two decisions, that's all
@.triage
Pick an opening and a brand level.
"""


# ------------------------------------------------------------------ parser

def test_the_format_matches_how_the_notes_are_already_written():
    beats = imp.parse(NOTES)
    assert [b["title"] for b in beats] == [
        "Start here 👋", "Your number, in your face", "Two decisions, that's all"]
    assert [b["target_selector"] for b in beats] == ["", ".phone", ".triage"]
    # A leading "!" is the only markup for "the client must approve this beat".
    assert [b["requires_approval"] for b in beats] == [False, False, True]
    # Wrapped prose becomes one paragraph rather than several lines.
    assert beats[1]["body_md"] == "Front and centre on every page, real size, tap-to-call."


def test_a_beat_needs_no_target():
    """A beat with no selector still reads — it just has no spotlight. Refusing
    it would make the format stricter than the notes people actually write."""
    beats = imp.parse("Just a thought\nSomething about the page.")
    assert beats[0]["target_selector"] == ""


def test_empty_and_titleless_input_is_refused_in_words():
    with pytest.raises(imp.ImportError_) as e:
        imp.parse("   \n\n  ")
    assert "no beats" in str(e.value)
    with pytest.raises(imp.ImportError_):
        imp.parse("!\n@.x\nA body with no title.")


# ------------------------------------------------------------------- route

def test_importing_appends_by_default(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    client.post(f"/api/options/{oid}/walkthrough", json={
        "title": "Hand-authored", "body_md": "…", "target_selector": ".x",
        "requires_approval": False})

    r = client.post(f"/api/options/{oid}/walkthrough/import", json={"text": NOTES})
    assert r.status_code == 200, r.text
    assert (r.json()["created"], r.json()["replaced"]) == (3, 0)

    steps = client.get(f"/api/options/{oid}/walkthrough").json()["steps"]
    assert [s["title"] for s in steps][0] == "Hand-authored"
    assert len(steps) == 4
    # Order continues from what was there rather than colliding with it.
    assert [s["step_order"] for s in steps] == sorted(s["step_order"] for s in steps)


def test_replace_wipes_first_and_takes_the_approvals_with_it(client, kit):
    """An approval belongs to the beat it was given on; a replaced beat is not
    the beat the client approved."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    step = client.post(f"/api/options/{oid}/walkthrough", json={
        "title": "Old beat", "body_md": "…", "target_selector": ".x",
        "requires_approval": True}).json()["id"]
    as_user(client, "cleo")
    client.post(f"/api/walkthrough/{step}/approve", json={})

    as_user(client, "mara")
    r = client.post(f"/api/options/{oid}/walkthrough/import",
                    json={"text": NOTES, "replace": True})
    assert (r.json()["created"], r.json()["replaced"]) == (3, 1)
    steps = client.get(f"/api/options/{oid}/walkthrough").json()["steps"]
    assert "Old beat" not in [s["title"] for s in steps]
    assert all(not s["client_approved"] for s in steps)


def test_replace_is_off_by_default(client, kit):
    """An import that silently destroyed an authored walkthrough would be
    unforgivable, so it has to be asked for."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    client.post(f"/api/options/{oid}/walkthrough", json={
        "title": "Keep me", "body_md": "…", "target_selector": ".x",
        "requires_approval": False})
    client.post(f"/api/options/{oid}/walkthrough/import", json={"text": NOTES})
    titles = [s["title"] for s in
              client.get(f"/api/options/{oid}/walkthrough").json()["steps"]]
    assert "Keep me" in titles


def test_a_bad_paste_is_explained_not_500ed(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    r = client.post(f"/api/options/{oid}/walkthrough/import", json={"text": "   "})
    assert r.status_code in (400, 422)


def test_importing_is_manager_only(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    as_user(client, "cleo")
    assert client.post(f"/api/options/{oid}/walkthrough/import",
                       json={"text": NOTES}).status_code == 403


# --------------------------------------------------------------- demo click

def test_a_beat_can_carry_a_demo_click():
    """Some designs only explain themselves when something is opened. `>` says
    what to click; a trailing number says how long to hold it open."""
    beats = imp.parse(
        "Try this: click Injuries\n@.triage\n>.tcell.injuries 5\n"
        "The grid becomes that team's space.")
    assert beats[0]["click_selector"] == ".tcell.injuries"
    assert beats[0]["click_dismiss_seconds"] == 5
    # The spotlight target and the thing being clicked stay separate: you often
    # point at one element and click another.
    assert beats[0]["target_selector"] == ".triage"
    assert beats[0]["body_md"] == "The grid becomes that team's space."


def test_a_demo_click_defaults_to_three_seconds():
    beats = imp.parse("Open it\n>.injuries\nWatch what happens.")
    assert (beats[0]["click_selector"], beats[0]["click_dismiss_seconds"]) == \
        (".injuries", 3)


def test_zero_seconds_means_leave_it_open():
    beats = imp.parse("Open it\n>.injuries 0\nStays open.")
    assert beats[0]["click_dismiss_seconds"] == 0


def test_beats_without_a_click_are_unaffected():
    beats = imp.parse("Just a note\n@.phone\nNothing is clicked here.")
    assert beats[0]["click_selector"] == ""
