import asyncio
import json

import pytest
from fastapi import HTTPException

from recoil.workspace import board_comments
from recoil.workspace.board_comments import BoardCommentsCorruptError
from recoil.workspace.tests._board_test_helpers import _json_body


class _FakeRequest:
    def __init__(self, payload=None):
        self._payload = payload if payload is not None else {}
        self._body = b"" if payload is None else json.dumps(payload).encode("utf-8")

    async def json(self):
        return self._payload

    async def body(self):
        return self._body


def test_comments_route_registered():
    from recoil.workspace.server import app

    routes = [r.path for r in app.routes if hasattr(r, "path")]
    assert "/api/episode/{project}/{episode_id}/comments" in routes
    assert "/api/episode/{project}/{episode_id}/comments/{comment_id}/resolve" in routes
    delete_routes = [
        r.path
        for r in app.routes
        if hasattr(r, "path") and "DELETE" in getattr(r, "methods", set())
    ]
    assert "/api/episode/{project}/{episode_id}/comments/{comment_id}" in delete_routes


def test_get_comments_empty(board_route_project):
    ws_server = board_route_project["server"]
    project = board_route_project["project"]

    response = asyncio.run(ws_server.get_episode_comments(project, "EP001"))
    body = _json_body(response)

    assert body == {"schema_version": 1, "episode_id": "EP001", "comments": []}


def test_get_comments_returns_added(board_route_project):
    ws_server = board_route_project["server"]
    project = board_route_project["project"]

    comment = board_comments.add_comment(
        project,
        "EP001",
        target_type="panel",
        batch_id="BATCH_001",
        segment_id="EP001_SH02",
        body="expand this reveal",
        tag="expand",
    )

    response = asyncio.run(ws_server.get_episode_comments(project, "EP001"))
    body = _json_body(response)

    assert body == {"schema_version": 1, "episode_id": "EP001", "comments": [comment]}


def test_get_comments_bad_project_400(board_route_project):
    ws_server = board_route_project["server"]

    with pytest.raises(HTTPException) as excinfo:
        asyncio.run(ws_server.get_episode_comments("../bad", "EP001"))

    assert excinfo.value.status_code == 400


def test_get_comments_unknown_project_404(board_route_project):
    ws_server = board_route_project["server"]

    response = asyncio.run(ws_server.get_episode_comments("missing_project", "EP001"))
    body = _json_body(response)

    assert response.status_code == 404
    assert body == {"error": "unknown project 'missing_project'"}


def test_get_comments_corrupt_file_propagates(board_route_project):
    ws_server = board_route_project["server"]
    project = board_route_project["project"]
    path = board_comments._comments_path(project, "EP001")
    path.write_bytes(b"{bad")

    with pytest.raises(BoardCommentsCorruptError):
        asyncio.run(ws_server.get_episode_comments(project, "EP001"))


def test_post_comment_creates_and_persists(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]

    response = asyncio.run(
        ws_server.post_episode_comment(
            project,
            "EP001",
            _FakeRequest(
                {
                    "target_type": "board",
                    "batch_id": "BATCH_001",
                    "body": "expand the setup",
                    "tag": "expand",
                    "author": "JT",
                }
            ),
        )
    )
    body = _json_body(response)

    assert response.status_code == 201
    assert body["id"].startswith("cmt_")
    assert board_comments._comments_path(project, "EP001").is_file()

    get_response = asyncio.run(ws_server.get_episode_comments(project, "EP001"))
    get_body = _json_body(get_response)
    assert get_body["comments"] == [body]


def test_post_panel_comment_missing_segment_400(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]

    response = asyncio.run(
        ws_server.post_episode_comment(
            project,
            "EP001",
            _FakeRequest(
                {
                    "target_type": "panel",
                    "batch_id": "BATCH_001",
                    "body": "panel note",
                    "tag": "note",
                }
            ),
        )
    )

    assert response.status_code == 400


def test_post_unknown_batch_id_400(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]
    path = board_comments._comments_path(project, "EP001")

    response = asyncio.run(
        ws_server.post_episode_comment(
            project,
            "EP001",
            _FakeRequest(
                {
                    "target_type": "board",
                    "batch_id": "BATCH_999",
                    "body": "orphan",
                    "tag": "note",
                }
            ),
        )
    )
    body = _json_body(response)

    assert response.status_code == 400
    assert "batch_id" in body["error"]
    assert not path.exists()


def test_post_unknown_segment_id_400(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]

    response = asyncio.run(
        ws_server.post_episode_comment(
            project,
            "EP001",
            _FakeRequest(
                {
                    "target_type": "panel",
                    "batch_id": "BATCH_001",
                    "segment_id": "EP001_SH99",
                    "body": "orphan panel",
                    "tag": "note",
                }
            ),
        )
    )
    body = _json_body(response)

    assert response.status_code == 400
    assert "segment_id" in body["error"]


def test_post_cross_batch_segment_400(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]
    path = board_comments._comments_path(project, "EP001")

    response = asyncio.run(
        ws_server.post_episode_comment(
            project,
            "EP001",
            _FakeRequest(
                {
                    "target_type": "panel",
                    "batch_id": "BATCH_001",
                    "segment_id": "EP001_SH03",
                    "body": "wrong board",
                    "tag": "note",
                }
            ),
        )
    )

    assert response.status_code == 400
    assert not path.exists()


def test_post_resolve_flips(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]

    create_response = asyncio.run(
        ws_server.post_episode_comment(
            project,
            "EP001",
            _FakeRequest(
                {
                    "target_type": "board",
                    "batch_id": "BATCH_001",
                    "body": "resolve me",
                    "tag": "note",
                }
            ),
        )
    )
    comment = _json_body(create_response)
    resolve_response = asyncio.run(
        ws_server.post_resolve_comment(
            project,
            "EP001",
            comment["id"],
            _FakeRequest({"resolved": True}),
        )
    )
    resolved = _json_body(resolve_response)

    assert resolved["resolved"] is True
    assert resolved["resolved_at"] is not None

    get_response = asyncio.run(ws_server.get_episode_comments(project, "EP001"))
    get_body = _json_body(get_response)
    assert get_body["comments"][0]["resolved"] is True


def test_post_resolve_unknown_404(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]

    response = asyncio.run(
        ws_server.post_resolve_comment(
            project,
            "EP001",
            "cmt_00000000000000000000000000000000",
            _FakeRequest({"resolved": True}),
        )
    )
    body = _json_body(response)

    assert response.status_code == 404
    assert body == {
        "error": "comment not found",
        "comment_id": "cmt_00000000000000000000000000000000",
    }


def test_post_comment_unknown_project_404(board_route_project):
    ws_server = board_route_project["server"]

    response = asyncio.run(
        ws_server.post_episode_comment(
            "missing_project",
            "EP001",
            _FakeRequest(
                {
                    "target_type": "board",
                    "batch_id": "BATCH_001",
                    "body": "note",
                    "tag": "note",
                }
            ),
        )
    )
    body = _json_body(response)

    assert response.status_code == 404
    assert body == {"error": "unknown project 'missing_project'"}


def test_post_resolve_unknown_project_404(board_route_project):
    ws_server = board_route_project["server"]

    response = asyncio.run(
        ws_server.post_resolve_comment(
            "missing_project",
            "EP001",
            "cmt_00000000000000000000000000000000",
            _FakeRequest({"resolved": True}),
        )
    )
    body = _json_body(response)

    assert response.status_code == 404
    assert body == {"error": "unknown project 'missing_project'"}


def test_delete_comment_removes_it(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]

    create_response = asyncio.run(
        ws_server.post_episode_comment(
            project,
            "EP001",
            _FakeRequest(
                {
                    "target_type": "board",
                    "batch_id": "BATCH_001",
                    "body": "delete me",
                    "tag": "note",
                }
            ),
        )
    )
    comment = _json_body(create_response)

    delete_response = asyncio.run(ws_server.delete_comment(project, "EP001", comment["id"]))
    removed = _json_body(delete_response)
    assert removed["id"] == comment["id"]

    get_body = _json_body(asyncio.run(ws_server.get_episode_comments(project, "EP001")))
    assert get_body["comments"] == []


def test_delete_comment_unknown_404(two_batch_board_route_project):
    ws_server = two_batch_board_route_project["server"]
    project = two_batch_board_route_project["project"]

    response = asyncio.run(
        ws_server.delete_comment(project, "EP001", "cmt_00000000000000000000000000000000")
    )
    body = _json_body(response)

    assert response.status_code == 404
    assert body == {
        "error": "comment not found",
        "comment_id": "cmt_00000000000000000000000000000000",
    }


def test_delete_comment_bad_project_400(board_route_project):
    ws_server = board_route_project["server"]

    with pytest.raises(HTTPException) as excinfo:
        asyncio.run(ws_server.delete_comment("../bad", "EP001", "cmt_x"))

    assert excinfo.value.status_code == 400


def test_delete_comment_unknown_project_404(board_route_project):
    ws_server = board_route_project["server"]

    response = asyncio.run(
        ws_server.delete_comment(
            "missing_project", "EP001", "cmt_00000000000000000000000000000000"
        )
    )
    body = _json_body(response)

    assert response.status_code == 404
    assert body == {"error": "unknown project 'missing_project'"}
