import asyncio
import json

import pytest


class _Request:
    def __init__(self, body: dict):
        self._body = body

    async def json(self):
        return self._body


@pytest.fixture
def isolated_state(tmp_path, monkeypatch):
    from recoil.workspace import server as ws_server

    ws_state = ws_server.ws_state
    monkeypatch.setattr(ws_state, "_STATE_DIR", tmp_path)
    monkeypatch.setattr(ws_state, "_STATE_PATH", tmp_path / "state.json")
    monkeypatch.setattr(ws_state, "_FLOCK_PATH", tmp_path / ".state.lock")
    return ws_server


def _tree_with_shots(*shot_ids: str) -> dict:
    return {
        "project": "test_project",
        "tree": {
            "name": "output",
            "type": "directory",
            "children": [
                {
                    "name": "Episodes",
                    "type": "directory",
                    "children": [
                        {
                            "name": "Episode 001",
                            "type": "directory",
                            "children": [
                                {
                                    "name": shot_id.rsplit("_", 1)[-1],
                                    "type": "shot",
                                    "shot_id": shot_id,
                                    "children": [],
                                }
                                for shot_id in shot_ids
                            ],
                        }
                    ],
                }
            ],
        },
        "file_count": len(shot_ids),
    }


def _json_body(response) -> dict:
    return json.loads(response.body.decode("utf-8"))


def test_selection_drops_bogus_shot_id_but_keeps_board_media_path(
    isolated_state, monkeypatch
):
    ws_server = isolated_state
    ws_server.ws_state.set_project("test_project")
    monkeypatch.setattr(
        ws_server,
        "build_project_tree",
        lambda project: _tree_with_shots("EP001_SH01"),
    )

    response = asyncio.run(
        ws_server.update_selection(
            _Request(
                {
                    "shot_ids": [
                        "EP001_SH01",
                        "EP001_SH99",
                        "prep/ep_001/storyboards/BATCH_001_v06.png",
                    ]
                }
            )
        )
    )

    accepted = ["EP001_SH01", "prep/ep_001/storyboards/BATCH_001_v06.png"]
    assert _json_body(response) == {"selection": accepted}
    assert ws_server.ws_state.read_state()["selection"] == accepted


def test_all_valid_selection_round_trips_via_get_state(isolated_state, monkeypatch):
    ws_server = isolated_state
    ws_server.ws_state.set_project("test_project")
    monkeypatch.setattr(
        ws_server,
        "build_project_tree",
        lambda project: _tree_with_shots("EP001_SH01", "EP001_SH02"),
    )

    response = asyncio.run(
        ws_server.update_selection(
            _Request({"shot_ids": ["EP001_SH01", "EP001_SH02"]})
        )
    )
    assert _json_body(response) == {"selection": ["EP001_SH01", "EP001_SH02"]}

    state_response = asyncio.run(ws_server.get_state())
    assert _json_body(state_response)["selection"] == ["EP001_SH01", "EP001_SH02"]


def test_empty_project_accepts_all_without_tree_validation(isolated_state, monkeypatch):
    ws_server = isolated_state

    def _fail_build_project_tree(project):
        raise AssertionError("empty project should not read the tree")

    monkeypatch.setattr(ws_server, "build_project_tree", _fail_build_project_tree)

    response = asyncio.run(
        ws_server.update_selection(
            _Request(
                {
                    "project": "",
                    "shot_ids": [
                        "EP001_SH99",
                        "prep/ep_001/storyboards/BATCH_001_v06.png",
                    ],
                }
            )
        )
    )

    accepted = ["EP001_SH99", "prep/ep_001/storyboards/BATCH_001_v06.png"]
    assert _json_body(response) == {"selection": accepted}
    assert ws_server.ws_state.read_state()["selection"] == accepted
