"""Tests for stamina cue broadcasts."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from mudlib.player import Player
from mudlib.zone import Zone


@pytest.fixture
def test_zone():
    """Override conftest test_zone with smaller zone for stamina tests."""
    return Zone(name="test", width=10, height=10)


@pytest.fixture
def player(mock_writer, test_zone):
    """Override conftest player with custom position and stats for stamina tests."""
    p = Player(
        name="Goku",
        x=5,
        y=5,
        pl=100.0,
        stamina=100.0,
        max_stamina=100.0,
        writer=mock_writer,
    )
    p.move_to(test_zone, x=5, y=5)
    return p


@pytest.fixture
def nearby_player(test_zone):
    """Override conftest nearby_player with custom position for stamina tests."""
    w = MagicMock()
    w.write = MagicMock()
    w.drain = AsyncMock()
    p = Player(
        name="Vegeta",
        x=5,
        y=5,
        pl=100.0,
        stamina=100.0,
        max_stamina=100.0,
        writer=w,
    )
    p.move_to(test_zone, x=5, y=5)
    return p


@pytest.mark.asyncio
async def test_no_cue_above_75_percent(player):
    """No cue when stamina is above 75%."""
    from mudlib.combat.stamina import check_stamina_cues

    player.stamina = 80.0
    player.max_stamina = 100.0

    await check_stamina_cues(player)

    # No message sent to player
    player.writer.write.assert_not_called()


@pytest.mark.asyncio
async def test_breathing_heavily_below_75_percent(player):
    """'Breathing heavily' cue when stamina drops below 75%."""
    from mudlib.combat.stamina import check_stamina_cues

    player.stamina = 70.0
    player.max_stamina = 100.0

    await check_stamina_cues(player)

    # Check self-directed message
    calls = [call[0][0] for call in player.writer.write.call_args_list]
    assert any("You're breathing heavily." in msg for msg in calls)


@pytest.mark.asyncio
async def test_drenched_in_sweat_below_50_percent(player):
    """'Drenched in sweat' cue when below 50%."""
    from mudlib.combat.stamina import check_stamina_cues

    player.stamina = 45.0
    player.max_stamina = 100.0

    await check_stamina_cues(player)

    calls = [call[0][0] for call in player.writer.write.call_args_list]
    assert any("You're drenched in sweat." in msg for msg in calls)


@pytest.mark.asyncio
async def test_visibly_shaking_below_25_percent(player):
    """'Visibly shaking' cue when below 25%."""
    from mudlib.combat.stamina import check_stamina_cues

    player.stamina = 20.0
    player.max_stamina = 100.0

    await check_stamina_cues(player)

    calls = [call[0][0] for call in player.writer.write.call_args_list]
    assert any("You're visibly shaking from exhaustion." in msg for msg in calls)


@pytest.mark.asyncio
async def test_can_barely_stand_below_10_percent(player):
    """'Can barely stand' cue when below 10%."""
    from mudlib.combat.stamina import check_stamina_cues

    player.stamina = 8.0
    player.max_stamina = 100.0

    await check_stamina_cues(player)

    calls = [call[0][0] for call in player.writer.write.call_args_list]
    assert any("You can barely stand." in msg for msg in calls)


@pytest.mark.asyncio
async def test_same_threshold_not_triggered_twice(player):
    """Same threshold doesn't trigger twice (spam prevention)."""
    from mudlib.combat.stamina import check_stamina_cues

    # First trigger at 70%
    player.stamina = 70.0
    player.max_stamina = 100.0
    await check_stamina_cues(player)
    first_call_count = player.writer.write.call_count

    # Second trigger at same threshold (still 70%)
    player.stamina = 70.0
    await check_stamina_cues(player)
    second_call_count = player.writer.write.call_count

    # No new messages should have been sent
    assert second_call_count == first_call_count


@pytest.mark.asyncio
async def test_higher_threshold_doesnt_retrigger(player):
    """Higher threshold doesn't trigger if already at lower."""
    from mudlib.combat.stamina import check_stamina_cues

    # Drop to 20% first
    player.stamina = 20.0
    player.max_stamina = 100.0
    await check_stamina_cues(player)
    player.writer.write.reset_mock()

    # Recover to 30% (back into 25-50% range)
    player.stamina = 30.0
    await check_stamina_cues(player)

    # No new cue should trigger
    player.writer.write.assert_not_called()


@pytest.mark.asyncio
async def test_self_directed_message_sent(player):
    """Self-directed message sent to the entity."""
    from mudlib.combat.stamina import check_stamina_cues

    player.stamina = 70.0
    player.max_stamina = 100.0

    await check_stamina_cues(player)

    # Verify self message was sent
    calls = [call[0][0] for call in player.writer.write.call_args_list]
    assert any("You're breathing heavily." in msg for msg in calls)


@pytest.mark.asyncio
async def test_nearby_broadcast_sent(player, nearby_player):
    """Nearby broadcast sent to other players."""
    from mudlib.combat.stamina import check_stamina_cues

    player.stamina = 70.0
    player.max_stamina = 100.0

    with patch("mudlib.combat.stamina.send_nearby_message") as mock_nearby:
        await check_stamina_cues(player)

        # Verify send_nearby_message was called
        mock_nearby.assert_called_once()
        args = mock_nearby.call_args[0]
        assert args[0] is player
        assert args[1] == 5  # x coordinate
        assert args[2] == 5  # y coordinate
        assert "Goku is breathing heavily." in args[3]


@pytest.mark.asyncio
async def test_descending_thresholds_trigger_each_once(player):
    """Descending through thresholds triggers each once."""
    from mudlib.combat.stamina import check_stamina_cues

    player.max_stamina = 100.0

    # Start at 80%, no cue
    player.stamina = 80.0
    await check_stamina_cues(player)
    count_80 = player.writer.write.call_count

    # Drop to 70%, should trigger 75% threshold
    player.stamina = 70.0
    await check_stamina_cues(player)
    count_70 = player.writer.write.call_count
    assert count_70 > count_80

    # Drop to 40%, should trigger 50% threshold
    player.stamina = 40.0
    await check_stamina_cues(player)
    count_40 = player.writer.write.call_count
    assert count_40 > count_70

    # Drop to 20%, should trigger 25% threshold
    player.stamina = 20.0
    await check_stamina_cues(player)
    count_20 = player.writer.write.call_count
    assert count_20 > count_40

    # Drop to 5%, should trigger 10% threshold
    player.stamina = 5.0
    await check_stamina_cues(player)
    count_5 = player.writer.write.call_count
    assert count_5 > count_20
