"""Tests for the presence file watcher in dashboard.app."""

import asyncio
import json
from unittest.mock import AsyncMock, patch

import pytest

# Common patches to eliminate delays and inject mock ws_manager
_FAST = {
    "dashboard.app._PRESENCE_POLL_SECONDS": 0.01,
    "dashboard.app._PRESENCE_INITIAL_DELAY": 0,
}


def _patches(mock_ws, presence_file):
    """Return a combined context manager for presence watcher patches."""
    from contextlib import ExitStack

    stack = ExitStack()
    stack.enter_context(patch("dashboard.app.PRESENCE_FILE", presence_file))
    stack.enter_context(patch("infrastructure.ws_manager.handler.ws_manager", mock_ws))
    for key, val in _FAST.items():
        stack.enter_context(patch(key, val))
    return stack


@pytest.mark.asyncio
async def test_presence_watcher_broadcasts_on_file_change(tmp_path):
    """Watcher reads presence.json and broadcasts via ws_manager."""
    from dashboard.app import _presence_watcher

    presence_file = tmp_path / "presence.json"
    state = {
        "version": 1,
        "liveness": "2026-03-18T21:47:12Z",
        "current_focus": "Reviewing PR #267",
        "mood": "focused",
    }
    presence_file.write_text(json.dumps(state))

    mock_ws = AsyncMock()

    with _patches(mock_ws, presence_file):
        task = asyncio.create_task(_presence_watcher())
        await asyncio.sleep(0.15)
        task.cancel()
        try:
            await task
        except asyncio.CancelledError:
            pass

    mock_ws.broadcast.assert_called_with("timmy_state", state)


@pytest.mark.asyncio
async def test_presence_watcher_synthesised_state_when_missing(tmp_path):
    """Watcher broadcasts synthesised idle state when file is absent."""
    from dashboard.app import _SYNTHESIZED_STATE, _presence_watcher

    missing_file = tmp_path / "no-such-file.json"
    mock_ws = AsyncMock()

    with _patches(mock_ws, missing_file):
        task = asyncio.create_task(_presence_watcher())
        await asyncio.sleep(0.15)
        task.cancel()
        try:
            await task
        except asyncio.CancelledError:
            pass

    mock_ws.broadcast.assert_called_with("timmy_state", _SYNTHESIZED_STATE)


@pytest.mark.asyncio
async def test_presence_watcher_handles_bad_json(tmp_path):
    """Watcher logs warning on malformed JSON and doesn't crash."""
    from dashboard.app import _presence_watcher

    presence_file = tmp_path / "presence.json"
    presence_file.write_text("{bad json!!!")
    mock_ws = AsyncMock()

    with _patches(mock_ws, presence_file):
        task = asyncio.create_task(_presence_watcher())
        await asyncio.sleep(0.15)
        task.cancel()
        try:
            await task
        except asyncio.CancelledError:
            pass

    # Should not have broadcast anything on bad JSON
    mock_ws.broadcast.assert_not_called()
