"""Atlas: the file-backed comments (store round-trip + the routes' authz)."""

from pathlib import Path

import pytest

from app.config import get_settings
from app.services import comments_store as cs
from tests.conftest import OWNER, as_user


# --------------------------------------------------------------- the store

def test_render_parse_round_trip(tmp_path: Path):
    store = cs.CommentsStore(tmp_path)
    t = store.add_thread("guide", "01-lab", "substrate", "adi", "Why one door?\n\nSecond para.")
    store.reply("guide", "01-lab", t.id, "rian", "Because audit.")
    t2 = store.add_thread("guide", "01-lab", "chapter", "adi", "Chapter-level note")
    store.set_status("guide", "01-lab", t2.id, "resolved")

    text = (tmp_path / "guide" / "01-lab.comments.md").read_text()
    assert "## [%s] section=substrate status=open" % t.id in text
    assert "- **adi** @ " in text and "  > Why one door?" in text and "  >\n" in text

    threads = store.threads("guide", "01-lab")
    assert [x.id for x in threads] == [t.id, t2.id]
    assert threads[0].entries[0].body == "Why one door?\n\nSecond para."
    assert threads[0].entries[1].author == "rian"
    assert threads[1].status == "resolved"


def test_hand_edited_file_is_tolerated(tmp_path: Path):
    """A Claude session (or the owner) replies by editing the file: the shape is
    the contract, anything outside it is ignored rather than fatal."""
    folder = tmp_path / "guide"
    folder.mkdir(parents=True)
    (folder / "02-flow.comments.md").write_text(
        "# Comments -- guide/02-flow\n\nsome stray prose\n\n"
        "## [t-abc12345] section=chapter status=open\n"
        "- **adi** @ 2026-09-05T11:40:00-07:00\n"
        "  > What is Caddy?\n"
        "a stray line\n"
        "- **claude** @ 2026-09-05T12:00:00-07:00\n"
        "  > The reverse proxy: it answers HTTPS and hands each request to the right container.\n"
        "## [not a thread]\n"
        "- **rian** @ now\n  > orphan\n")
    threads = cs.CommentsStore(tmp_path).threads("guide", "02-flow")
    assert len(threads) == 1
    assert [e.author for e in threads[0].entries] == ["adi", "claude"]
    assert threads[0].entries[1].body.startswith("The reverse proxy")


def test_delete_last_entry_removes_thread(tmp_path: Path):
    store = cs.CommentsStore(tmp_path)
    t = store.add_thread("guide", "x", "chapter", "adi", "hi")
    assert store.delete_entry("guide", "x", t.id, 0) is None
    assert store.threads("guide", "x") == []


def test_slug_validation_blocks_traversal(tmp_path: Path):
    store = cs.CommentsStore(tmp_path)
    with pytest.raises(cs.CommentsError):
        store.threads("guide", "../etc")
    with pytest.raises(cs.CommentsError):
        store.add_thread("guide", "ok", "Bad Section", "adi", "x")
    with pytest.raises(cs.CommentsError):
        store.add_thread("guide", "ok", "s", "adi", "x" * (cs.MAX_BODY_CHARS + 1))


def test_files_are_group_writable(tmp_path: Path):
    store = cs.CommentsStore(tmp_path)
    store.add_thread("guide", "perm", "chapter", "adi", "hi")
    mode = (tmp_path / "guide" / "perm.comments.md").stat().st_mode & 0o777
    assert mode & 0o060 == 0o060, oct(mode)


# --------------------------------------------------------------- the routes

def test_anonymous_is_denied(client):
    assert client.get("/api/comments/01-lab").status_code == 401
    assert client.post("/api/comments/01-lab",
                       json={"section": "chapter", "body": "x"}).status_code == 401


def test_signed_in_non_member_is_refused(client):
    """A BW account that exists but was never added to atlas: 403 NOT_A_MEMBER, and
    whoami says is_member false so the SPA shows the no-access screen."""
    as_user(client, "stranger")
    r = client.get("/api/comments/01-lab")
    assert r.status_code == 403
    assert r.json()["detail"]["error_code"] == "NOT_A_MEMBER"
    who = client.get("/api/whoami").json()
    assert who["is_member"] is False and who["can_moderate"] is False


def test_member_flow_and_moderation(client, kit):
    kit.member("adi", "member")
    as_user(client, "adi")
    who = client.get("/api/whoami").json()
    assert who["is_member"] is True and who["can_moderate"] is False

    r = client.post("/api/comments/01-lab", json={"section": "substrate", "body": "Why?"})
    assert r.status_code == 201, r.text
    tid = r.json()["thread"]["id"]
    assert r.json()["thread"]["entries"][0]["author"] == "adi"   # author = session user

    # the starter may resolve their own thread
    assert client.post(f"/api/comments/01-lab/{tid}/status",
                       json={"status": "resolved"}).status_code == 200

    # a second member may reply but not resolve/delete someone else's
    kit.member("bob", "member")
    as_user(client, "bob")
    assert client.post(f"/api/comments/01-lab/{tid}/reply",
                       json={"body": "Same question."}).status_code == 200
    assert client.post(f"/api/comments/01-lab/{tid}/status",
                       json={"status": "open"}).status_code == 403
    assert client.delete(f"/api/comments/01-lab/{tid}/0").status_code == 403
    assert client.delete(f"/api/comments/01-lab/{tid}/1").status_code == 200  # own

    # the owner moderates anything
    as_user(client, OWNER)
    assert client.get("/api/comments/01-lab").json()["can_moderate"] is True
    assert client.post(f"/api/comments/01-lab/{tid}/status",
                       json={"status": "open"}).status_code == 200
    counts = client.get("/api/comments").json()["counts"]
    assert counts["01-lab"] == {"open": 1, "total": 1}
    assert client.delete(f"/api/comments/01-lab/{tid}/0").status_code == 200
    assert client.get("/api/comments/01-lab").json()["threads"] == []


def test_file_lands_in_the_data_dir(client, kit):
    kit.member("adi", "member")
    as_user(client, "adi")
    client.post("/api/comments/05-shape", json={"section": "chapter", "body": "hello"})
    path = get_settings().data_dir / "comments" / "guide" / "05-shape.comments.md"
    assert path.is_file()
    assert "- **adi** @ " in path.read_text()
