"""Tests for WatcherService and SSE watch endpoint."""

import asyncio
import json
from pathlib import Path

import pytest

from hangar.services.watcher import WatcherService


class TestWatcherService:
    """Unit tests for WatcherService."""

    def test_register_deregister_client(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        assert service.client_count == 0

        q1 = service.register_client()
        assert service.client_count == 1

        q2 = service.register_client()
        assert service.client_count == 2

        service.deregister_client(q1)
        assert service.client_count == 1

        service.deregister_client(q2)
        assert service.client_count == 0

    def test_deregister_unknown_client_is_noop(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        q = asyncio.Queue()
        service.deregister_client(q)
        assert service.client_count == 0

    def test_broadcast_sends_to_all_clients(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        q1 = service.register_client()
        q2 = service.register_client()

        service._broadcast("tasks_changed")

        msg1 = q1.get_nowait()
        msg2 = q2.get_nowait()

        assert "tasks_changed" in msg1
        assert "tasks_changed" in msg2

        for msg in (msg1, msg2):
            lines = msg.strip().split("\n")
            assert lines[0] == "event: tasks_changed"
            data = json.loads(lines[1].replace("data: ", ""))
            assert data["type"] == "tasks_changed"
            assert "timestamp" in data

    def test_broadcast_specs_changed(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        q = service.register_client()

        service._broadcast("specs_changed")

        msg = q.get_nowait()
        assert "specs_changed" in msg
        data = json.loads(msg.strip().split("\n")[1].replace("data: ", ""))
        assert data["type"] == "specs_changed"

    def test_classify_change_tasks(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        assert service._classify_change("/project/.beads/issues.jsonl") == "tasks_changed"
        assert service._classify_change("/project/something.beads") == "tasks_changed"

    def test_classify_change_specs(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        assert service._classify_change("/project/openspec/changes/feature/tasks.md") == "specs_changed"
        assert service._classify_change("/project/openspec/specs/auth/spec.md") == "specs_changed"

    def test_classify_change_unknown(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        assert service._classify_change("/project/src/main.py") is None

    @pytest.mark.asyncio
    async def test_start_stop(self, tmp_path: Path):
        (tmp_path / ".beads").mkdir()
        (tmp_path / "openspec").mkdir()

        service = WatcherService(project_path=tmp_path)
        await service.start()
        assert service._watch_task is not None
        assert not service._watch_task.done()

        await service.stop()
        assert service._watch_task is None

    @pytest.mark.asyncio
    async def test_start_without_directories(self, tmp_path: Path):
        service = WatcherService(project_path=tmp_path)
        await service.start()
        await asyncio.sleep(0.1)
        await service.stop()

    def test_broadcast_drops_full_queues(self, tmp_path: Path):
        """When a client queue is full, broadcast should drop it."""
        service = WatcherService(project_path=tmp_path)
        q = service.register_client()

        # Fill the queue
        for _ in range(1000):
            try:
                q.put_nowait("filler")
            except asyncio.QueueFull:
                break

        # Broadcast should not raise
        service._broadcast("tasks_changed")

    @pytest.mark.asyncio
    async def test_stop_cancels_debounce_timers(self, tmp_path: Path):
        (tmp_path / ".beads").mkdir()
        service = WatcherService(project_path=tmp_path)
        await service.start()

        # Trigger a debounced event
        service._debounced_broadcast("tasks_changed")
        assert len(service._debounce_timers) > 0

        await service.stop()
        assert len(service._debounce_timers) == 0


class TestWatchSSERouter:
    """Tests for the SSE /api/watch router — unit-level tests."""

    def test_watch_endpoint_registered(self):
        """The /api/watch endpoint should be registered."""
        from hangar.main import app
        paths = [r.path for r in app.routes if hasattr(r, "path")]
        assert "/api/watch" in paths

    def test_watcher_service_setter(self):
        """set_watcher_service/get_watcher_service roundtrip."""
        from hangar.routers.watch import set_watcher_service, get_watcher_service
        from hangar.services.watcher import WatcherService
        from pathlib import Path

        svc = WatcherService(project_path=Path("/tmp"))
        set_watcher_service(svc)
        assert get_watcher_service() is svc
