"""The board as the project's journey: stages (caddie's shape, seeded from the
roadmap template), the client's "Feedback complete", the conversation about a
concept as a whole, and concept blurbs + thumbnails."""

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

PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 24
ROADMAP = ["scope", "concepts", "revision", "inner-pages", "signoff", "development"]


def test_a_project_starts_with_the_roadmap(client, kit):
    iid = make_project(client, kit)
    stages = client.get(f"/api/projects/{iid}/stages").json()["stages"]
    assert [s["key"] for s in stages] == ROADMAP
    assert [s["status"] for s in stages] == ["done", "active"] + ["planned"] * 4
    assert stages[0]["closed_at"] and stages[1]["started_at"]
    assert stages[2]["started_at"] is None
    # the same list rides on the project detail
    detail = client.get(f"/api/projects/{iid}").json()
    assert [s["key"] for s in detail["stages"]] == ROADMAP


def test_a_manager_moves_a_stage_and_the_client_sees_only_what_is_visible(client, kit):
    iid = make_project(client, kit)
    stages = client.get(f"/api/projects/{iid}/stages").json()["stages"]
    concepts, revision, development = stages[1], stages[2], stages[5]
    r = client.patch(f"/api/projects/{iid}/stages/{concepts['id']}", json={"status": "done"})
    assert r.status_code == 200 and r.json()["closed_at"]
    r = client.patch(f"/api/projects/{iid}/stages/{revision['id']}",
                     json={"status": "active", "body_md": "Refining **The Direct Answer**."})
    assert r.status_code == 200 and r.json()["started_at"] and r.json()["closed_at"] is None
    assert client.patch(f"/api/projects/{iid}/stages/{development['id']}",
                        json={"client_visible": False}).status_code == 200

    as_user(client, "cleo")
    seen = client.get(f"/api/projects/{iid}/stages").json()["stages"]
    assert [s["key"] for s in seen] == ROADMAP[:-1]
    assert seen[2]["status"] == "active" and "Direct Answer" in seen[2]["body_md"]
    # the client cannot move a stage; an outsider cannot see the roadmap
    assert client.patch(f"/api/projects/{iid}/stages/{revision['id']}",
                        json={"status": "done"}).status_code == 403
    as_user(client, "nate")
    assert client.get(f"/api/projects/{iid}/stages").status_code == 404


def test_feedback_complete_turns_the_board_and_tells_the_team(client, kit):
    iid = make_project(client, kit)
    add_screen_option(client, iid)
    client.post(f"/api/projects/{iid}/send")
    assert client.get(f"/api/projects/{iid}/status").json()["state"] == "waiting_client"

    as_user(client, "cleo")
    assert client.post(f"/api/projects/{iid}/feedback-complete").status_code == 200
    detail = client.get(f"/api/projects/{iid}").json()
    assert detail["feedback_completed_by"] == "cleo" and detail["feedback_completed_at"]
    assert detail["state"] == "waiting_agency"

    as_user(client, "mara")
    bell = client.get("/api/notifications").json()
    assert any(n["kind"] == "turn" and "cleo" in n["body"] for n in bell["notifications"])
    # presenting again opens a new round
    client.post(f"/api/projects/{iid}/send")
    detail = client.get(f"/api/projects/{iid}").json()
    assert detail["feedback_completed_at"] is None and detail["state"] == "waiting_client"


def test_feedback_complete_does_not_undo_done(client, kit):
    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})
    client.post(f"/api/projects/{iid}/feedback-complete")
    assert client.get(f"/api/projects/{iid}/status").json()["state"] == "done"


def test_a_concept_has_a_conversation_of_its_own(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    client.post(f"/api/projects/{iid}/send")
    as_user(client, "cleo")
    assert client.get(f"/api/options/{oid}/thread").json() == {
        "thread_id": None, "resolved": False, "comments": []}
    tid = client.post(f"/api/options/{oid}/comments",
                      json={"body_md": "This one, but the header feels heavy."}).json()["id"]
    assert client.post(f"/api/options/{oid}/comments",
                       json={"body_md": "And a bigger phone number."}).json()["id"] == tid
    o = client.get(f"/api/projects/{iid}").json()["screens"][0]["options"][0]
    assert (o["comment_count"], o["open_comments"]) == (2, 1)
    assert client.get(f"/api/projects/{iid}/status").json()["state"] == "waiting_agency"

    as_user(client, "mara")
    client.post(f"/api/threads/{tid}/resolve")
    o = client.get(f"/api/projects/{iid}").json()["screens"][0]["options"][0]
    assert (o["comment_count"], o["open_comments"]) == (2, 0)
    as_user(client, "nate")
    assert client.get(f"/api/options/{oid}/thread").status_code == 404


def test_a_concept_can_carry_a_blurb_and_a_picture(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    assert client.patch(f"/api/options/{oid}", json={
        "blurb": "No hero: the practice areas are the first screen."}).status_code == 200
    r = client.post(f"/api/options/{oid}/thumbnail",
                    files={"file": ("shot.png", PNG, "image/png")})
    assert r.status_code == 200, r.text
    url = r.json()["thumbnail_url"]
    o = client.get(f"/api/projects/{iid}").json()["screens"][0]["options"][0]
    assert o["blurb"].startswith("No hero") and o["thumbnail_url"] == url
    got = client.get(url)
    assert got.status_code == 200
    assert got.headers["content-type"].startswith("image/png") and got.content == PNG
    # not a picture -> refused; the client cannot set one at all, but can see it
    assert client.post(f"/api/options/{oid}/thumbnail",
                       files={"file": ("brief.pdf", b"%PDF-1.4\n", "application/pdf")}
                       ).status_code == 400
    as_user(client, "cleo")
    assert client.post(f"/api/options/{oid}/thumbnail",
                       files={"file": ("shot.png", PNG, "image/png")}).status_code == 403
    assert client.get(url).status_code == 200


def test_the_discussion_read_carries_every_subject(client, kit):
    """One read for the viewer's Discussion mode: pins, the page, the version,
    the walkthrough points — each with its conversation."""
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    step = client.post(f"/api/options/{oid}/walkthrough",
                       json={"title": "The five doors", "body_md": "x",
                             "rect": {"x": 0, "y": 8, "w": 100, "h": 40}}).json()["id"]
    client.post(f"/api/projects/{iid}/send")
    as_user(client, "cleo")
    client.post(f"/api/options/{oid}/pins",
                json={"x_percent": 40.0, "y_percent": 12.0, "body_md": "This bit."})
    client.post(f"/api/screens/{sid}/comments", json={"body_md": "About the page."})
    client.post(f"/api/options/{oid}/comments", json={"body_md": "About this version."})
    client.post(f"/api/steps/{step}/comments", json={"body_md": "About the doors."})

    d = client.get(f"/api/options/{oid}/discussion").json()
    assert [p["number"] for p in d["pins"]] == [1]
    assert d["page"]["comments"][0]["body_md"] == "About the page."
    assert d["version"]["comments"][0]["body_md"] == "About this version."
    assert [(p["title"], p["thread"]["comments"][0]["body_md"]) for p in d["points"]] == [
        ("The five doors", "About the doors.")]
    as_user(client, "nate")
    assert client.get(f"/api/options/{oid}/discussion").status_code == 404


def test_a_note_can_mark_an_area(client, kit):
    """"This whole block" is a different remark from "this spot": a note may
    carry the area it is about; a plain click carries none."""
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    client.post(f"/api/projects/{iid}/send")
    as_user(client, "cleo")
    area = client.post(f"/api/options/{oid}/pins", json={
        "x_percent": 10.0, "y_percent": 20.0, "w_percent": 30.0, "h_percent": 12.5,
        "body_md": "This whole block feels heavy."}).json()["pin"]
    point = client.post(f"/api/options/{oid}/pins", json={
        "x_percent": 55.0, "y_percent": 5.0, "body_md": "This spot."}).json()["pin"]
    assert (area["w_percent"], area["h_percent"]) == (30.0, 12.5)
    assert (point["w_percent"], point["h_percent"]) == (None, None)
    pins = client.get(f"/api/options/{oid}/pins").json()["pins"]
    assert [(p["number"], p["w_percent"]) for p in pins] == [(1, 30.0), (2, None)]
    # a size needs both dimensions, and neither may be zero
    r = client.post(f"/api/options/{oid}/pins", json={
        "x_percent": 1.0, "y_percent": 1.0, "w_percent": 0, "h_percent": 5, "body_md": "x"})
    assert r.status_code == 422


def test_a_note_can_be_moved_and_an_area_resized_after_posting(client, kit):
    iid = make_project(client, kit)
    _sid, oid = add_screen_option(client, iid)
    client.post(f"/api/projects/{iid}/send")
    as_user(client, "cleo")
    area = client.post(f"/api/options/{oid}/pins", json={
        "x_percent": 10.0, "y_percent": 20.0, "w_percent": 30.0, "h_percent": 10.0,
        "body_md": "This block."}).json()["pin"]
    # move keeps the size; move + size changes both
    assert client.post(f"/api/pins/{area['pin_id']}/move",
                       json={"x_percent": 12.0, "y_percent": 22.0}).status_code == 200
    p = client.get(f"/api/options/{oid}/pins").json()["pins"][0]
    assert (p["x_percent"], p["w_percent"], p["h_percent"]) == (12.0, 30.0, 10.0)
    assert client.post(f"/api/pins/{area['pin_id']}/move", json={
        "x_percent": 5.0, "y_percent": 5.0, "w_percent": 50.0, "h_percent": 25.0}).status_code == 200
    p = client.get(f"/api/options/{oid}/pins").json()["pins"][0]
    assert (p["x_percent"], p["w_percent"], p["h_percent"]) == (5.0, 50.0, 25.0)
    # someone else's note stays where they put it
    as_user(client, "nate")
    assert client.post(f"/api/pins/{area['pin_id']}/move",
                       json={"x_percent": 1.0, "y_percent": 1.0}).status_code == 404


def test_a_conversation_can_be_refiled_under_another_subject(client, kit):
    """A note on a spot that turns out to be about the whole page moves to the
    page; its comments keep their authors and times, the marker goes; moving
    onto a subject someone already spoke about joins that conversation."""
    iid = make_project(client, kit)
    sid, oid = add_screen_option(client, iid)
    step = client.post(f"/api/options/{oid}/walkthrough",
                       json={"title": "The five doors", "body_md": "x",
                             "rect": {"x": 0, "y": 8, "w": 100, "h": 40}}).json()["id"]
    client.post(f"/api/projects/{iid}/send")
    as_user(client, "cleo")
    pin = client.post(f"/api/options/{oid}/pins", json={
        "x_percent": 40.0, "y_percent": 12.0, "body_md": "Actually, the whole page."}).json()["pin"]
    client.post(f"/api/threads/{pin['thread_id']}/comments", json={"body_md": "Every version, really."})

    # pin -> page (nobody had spoken about the page): the thread becomes the page's
    r = client.post(f"/api/threads/{pin['thread_id']}/move",
                    json={"subject_type": "screen", "subject_id": sid})
    assert r.status_code == 200, r.text
    assert client.get(f"/api/options/{oid}/pins").json()["pins"] == []
    page = client.get(f"/api/screens/{sid}/thread").json()
    assert [c["body_md"] for c in page["comments"]] == [
        "Actually, the whole page.", "Every version, really."]
    assert page["comments"][0]["author_side"] == "client"

    # page -> step, where the team had already left a comment: the two merge
    as_user(client, "mara")
    client.post(f"/api/steps/{step}/comments", json={"body_md": "About the doors."})
    r = client.post(f"/api/threads/{page['thread_id']}/move",
                    json={"subject_type": "step", "subject_id": step})
    assert r.status_code == 200, r.text
    assert client.get(f"/api/screens/{sid}/thread").json()["thread_id"] is None
    point = client.get(f"/api/steps/{step}/thread").json()
    assert sorted(c["body_md"] for c in point["comments"]) == sorted([
        "About the doors.", "Actually, the whole page.", "Every version, really."])
    d = client.get(f"/api/options/{oid}/discussion").json()
    assert d["pins"] == [] and d["page"]["thread_id"] is None
    assert len(d["points"][0]["thread"]["comments"]) == 3

    # only the author or the team; and only within this project
    as_user(client, "nate")
    r = client.post(f"/api/threads/{point['thread_id']}/move",
                    json={"subject_type": "option", "subject_id": oid})
    assert r.status_code == 404, r.text
    # another person on the client side may not move the team's conversation
    as_user(client, "cleo")
    assert client.post(f"/api/threads/{point['thread_id']}/move",
                       json={"subject_type": "option", "subject_id": oid}).status_code == 403
    as_user(client, "mara")
    assert client.post(f"/api/threads/{point['thread_id']}/move",
                       json={"subject_type": "screen", "subject_id": 999}).status_code == 404


def test_the_team_can_say_the_introduction_its_own_way(client, kit):
    """The introduction's points ship with the app; a project overrides a
    point's words, and clearing both fields brings the shipped words back."""
    iid = make_project(client, kit)
    r = client.patch(f"/api/projects/{iid}/tour/stages",
                     json={"body": "A few first concepts, homepage only, {name}."})
    assert r.status_code == 200, r.text
    assert r.json()["tour_overrides"] == {"stages": {"body": "A few first concepts, homepage only, {name}."}}
    assert client.get(f"/api/projects/{iid}").json()["tour_overrides"]["stages"]["body"].startswith("A few")
    assert client.patch(f"/api/projects/{iid}/tour/stages",
                        json={"title": "Where we are now", "body": ""}).json()["tour_overrides"] == {
        "stages": {"title": "Where we are now"}}
    assert client.patch(f"/api/projects/{iid}/tour/stages", json={"title": ""}).json()["tour_overrides"] == {}
    as_user(client, "cleo")
    assert client.patch(f"/api/projects/{iid}/tour/stages", json={"title": "x"}).status_code == 403
