"""Tests for mob pathfinding back to home region."""

import time
from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.entity import Mob
from mudlib.mob_ai import process_mob_movement
from mudlib.mobs import mobs
from mudlib.zone import Zone


@pytest.fixture(autouse=True)
def clear_mobs():
    """Clear global mobs list before each test."""
    mobs.clear()
    yield
    mobs.clear()


@pytest.fixture
def zone():
    """Create a simple test zone."""
    terrain = [
        [".", ".", ".", ".", "."],
        [".", ".", "#", ".", "."],
        [".", ".", ".", ".", "."],
        [".", ".", "#", ".", "."],
        [".", ".", ".", ".", "."],
    ]
    return Zone(
        name="test",
        description="Test Zone",
        width=5,
        height=5,
        terrain=terrain,
        toroidal=False,
        impassable={"#"},
    )


@pytest.mark.asyncio
async def test_mob_outside_home_region_moves_toward_it(zone):
    """Mob outside home region should move one tile toward center."""
    # Create mob at (4, 4) with home region center at (1, 1)
    mob = Mob(
        name="test_mob",
        location=zone,
        x=4,
        y=4,
        home_x_min=0,
        home_x_max=2,
        home_y_min=0,
        home_y_max=2,
    )
    mobs.append(mob)

    # First movement should happen immediately
    await process_mob_movement()

    # Mob should move toward home (prefer larger distance axis)
    # Distance to center (1,1): dx=3, dy=3 — equal, so prefer x-axis
    assert mob.x == 3 and mob.y == 4


@pytest.mark.asyncio
async def test_mob_inside_home_region_does_not_move(zone):
    """Mob already inside home region should not move."""
    mob = Mob(
        name="test_mob",
        location=zone,
        x=1,
        y=1,
        home_x_min=0,
        home_x_max=2,
        home_y_min=0,
        home_y_max=2,
    )
    mobs.append(mob)

    await process_mob_movement()

    # Position should not change
    assert mob.x == 1 and mob.y == 1


@pytest.mark.asyncio
async def test_mob_with_no_home_region_does_not_move(zone):
    """Mob with no home region should not move."""
    mob = Mob(
        name="test_mob",
        location=zone,
        x=2,
        y=2,
    )
    mobs.append(mob)

    await process_mob_movement()

    # Position should not change
    assert mob.x == 2 and mob.y == 2


@pytest.mark.asyncio
async def test_mob_in_combat_does_not_move(zone):
    """Mob in an encounter should not move."""
    from mudlib.combat.encounter import CombatEncounter

    mob = Mob(
        name="test_mob",
        location=zone,
        x=4,
        y=4,
        home_x_min=0,
        home_x_max=2,
        home_y_min=0,
        home_y_max=2,
    )
    mobs.append(mob)

    # Create a mock player for the encounter
    from mudlib.player import Player

    mock_writer = MagicMock()
    mock_writer.is_closing.return_value = False
    mock_writer.drain = AsyncMock()

    player = Player(
        name="test_player",
        location=zone,
        x=4,
        y=4,
        writer=mock_writer,
        reader=MagicMock(),
    )

    # Create encounter
    from mudlib.combat.engine import active_encounters

    encounter = CombatEncounter(attacker=mob, defender=player)
    active_encounters.append(encounter)

    try:
        await process_mob_movement()

        # Mob should not move
        assert mob.x == 4 and mob.y == 4
    finally:
        active_encounters.clear()


@pytest.mark.asyncio
async def test_movement_is_throttled(zone):
    """Mob should not move on every tick (throttle ~3 seconds)."""
    mob = Mob(
        name="test_mob",
        location=zone,
        x=4,
        y=4,
        home_x_min=0,
        home_x_max=2,
        home_y_min=0,
        home_y_max=2,
    )
    mobs.append(mob)

    # First move should happen
    await process_mob_movement()
    first_x, first_y = mob.x, mob.y
    assert (first_x, first_y) != (4, 4)  # Moved

    # Immediate second call should not move
    await process_mob_movement()
    assert mob.x == first_x and mob.y == first_y

    # After waiting 3 seconds, should move again
    time.sleep(3.1)
    await process_mob_movement()
    assert mob.x != first_x or mob.y != first_y


@pytest.mark.asyncio
async def test_mob_respects_impassable_terrain(zone):
    """Mob should not move into impassable terrain."""
    # Position mob at (1, 0) with home at (3, 0)
    # There's a wall at (2, 1) but path around it
    mob = Mob(
        name="test_mob",
        location=zone,
        x=1,
        y=0,
        home_x_min=3,
        home_x_max=4,
        home_y_min=0,
        home_y_max=1,
    )
    mobs.append(mob)

    await process_mob_movement()

    # Mob should move toward home (east)
    assert mob.x == 2 and mob.y == 0

    # Try to move south from (2,0) — wall at (2,1)
    mob.x = 2
    mob.y = 0
    mob.home_x_min = 2
    mob.home_x_max = 2
    mob.home_y_min = 2
    mob.home_y_max = 4
    time.sleep(3.1)  # Wait for throttle

    await process_mob_movement()

    # Should not have moved into wall (2,1 is '#')
    # Should stay at (2, 0) since target (2,1) is blocked
    assert mob.x == 2 and mob.y == 0


@pytest.mark.asyncio
async def test_movement_broadcasts_to_nearby_players(zone):
    """Mob movement should broadcast to nearby players."""
    from mudlib.player import Player

    mob = Mob(
        name="test_mob",
        location=zone,
        x=2,
        y=2,
        home_x_min=0,
        home_x_max=1,
        home_y_min=0,
        home_y_max=1,
    )
    mobs.append(mob)

    # Create a player nearby
    mock_writer = MagicMock()
    mock_writer.is_closing.return_value = False
    mock_writer.drain = AsyncMock()

    player = Player(
        name="test_player",
        location=zone,
        x=2,
        y=2,
        writer=mock_writer,
        reader=MagicMock(),
    )

    await process_mob_movement()

    # Player should receive a movement message
    assert player.writer.write.called
    # Check that message contains mob name and direction
    call_args = [call[0][0] for call in player.writer.write.call_args_list]
    movement_msg = "".join(call_args)
    assert "test_mob" in movement_msg
