"""Tests for selection_routes — /api/selection/current GET + POST.

Six invariants:
  1. Fresh start returns {}.
  2. POST then GET returns the posted payload.
  3. POST with only project_id — take_id absent from stored dict.
  4. Two POSTs — second overwrites first entirely.
  5. POST emits a BUS event with the right args.
  6. null fields are excluded from the stored dict.
"""
from __future__ import annotations

from unittest.mock import patch

import pytest
from fastapi.testclient import TestClient

import recoil.api.selection_routes as sel_mod
from recoil.api.eventbus import BUS
from recoil.api.main import app


@pytest.fixture(autouse=True)
def _reset_selection():
    """Clear _SELECTION and BUS state before every test."""
    with sel_mod._LOCK:
        sel_mod._SELECTION.clear()
    BUS._reset_for_tests()
    yield
    with sel_mod._LOCK:
        sel_mod._SELECTION.clear()
    BUS._reset_for_tests()


@pytest.fixture
def client():
    """TestClient as a context manager — invokes lifespan so BUS binds."""
    with TestClient(app) as c:
        yield c


# ── 1 ────────────────────────────────────────────────────────────────────────


def test_get_selection_empty(client: TestClient) -> None:
    r = client.get("/api/selection/current")
    assert r.status_code == 200
    assert r.json() == {}


# ── 2 ────────────────────────────────────────────────────────────────────────


def test_set_and_get_selection(client: TestClient) -> None:
    payload = {"project_id": "tartarus", "beat_id": "EP001_SH02", "take_id": "T001"}
    post_r = client.post("/api/selection/current", json=payload)
    assert post_r.status_code == 200
    assert post_r.json() == {"ok": True}

    get_r = client.get("/api/selection/current")
    assert get_r.status_code == 200
    assert get_r.json() == payload


# ── 3 ────────────────────────────────────────────────────────────────────────


def test_set_selection_partial(client: TestClient) -> None:
    client.post("/api/selection/current", json={"project_id": "tartarus"})
    r = client.get("/api/selection/current")
    data = r.json()
    assert data["project_id"] == "tartarus"
    assert "take_id" not in data
    assert "beat_id" not in data


# ── 4 ────────────────────────────────────────────────────────────────────────


def test_set_selection_clears_old(client: TestClient) -> None:
    client.post(
        "/api/selection/current",
        json={"project_id": "tartarus", "beat_id": "EP001_SH01", "take_id": "T001"},
    )
    client.post(
        "/api/selection/current",
        json={"project_id": "starsend", "beat_id": "EP002_SH05"},
    )
    r = client.get("/api/selection/current")
    data = r.json()
    assert data == {"project_id": "starsend", "beat_id": "EP002_SH05"}
    assert "take_id" not in data


# ── 5 ────────────────────────────────────────────────────────────────────────


def test_set_selection_emits_bus_event(client: TestClient) -> None:
    payload = {"project_id": "tartarus", "beat_id": "EP001_SH02", "take_id": "T001"}
    with patch.object(BUS, "emit_sync") as mock_emit:
        client.post("/api/selection/current", json=payload)
    mock_emit.assert_called_once_with(
        "info",
        "selection/changed",
        "selection updated",
        payload=payload,
    )


# ── 6 ────────────────────────────────────────────────────────────────────────


def test_set_selection_null_fields_excluded(client: TestClient) -> None:
    client.post(
        "/api/selection/current",
        json={"project_id": "tartarus", "beat_id": None, "take_id": None},
    )
    r = client.get("/api/selection/current")
    data = r.json()
    assert data == {"project_id": "tartarus"}
    assert "beat_id" not in data
    assert "take_id" not in data
