"""Tests for NPC behavior state machine."""

import pytest

from mudlib.entity import Mob
from mudlib.npc_behavior import (
    VALID_STATES,
    get_flee_direction,
    get_patrol_direction,
    process_behavior,
    transition_state,
)
from mudlib.zone import Zone


@pytest.fixture
def test_zone():
    """Create a simple test zone for behavior tests."""
    terrain = [["." for _ in range(256)] for _ in range(256)]
    zone = Zone(
        name="testzone",
        width=256,
        height=256,
        toroidal=True,
        terrain=terrain,
        impassable=set(),
    )
    return zone


@pytest.fixture
def mob(test_zone):
    """Create a basic mob for testing."""
    m = Mob(name="test mob", x=10, y=10)
    m.location = test_zone
    return m


class TestTransitionState:
    def test_valid_transition_sets_state(self, mob):
        """transition_state with valid state sets behavior_state."""
        result = transition_state(mob, "patrol")
        assert result is True
        assert mob.behavior_state == "patrol"

    def test_valid_transition_sets_data(self, mob):
        """transition_state sets behavior_data when provided."""
        data = {"waypoints": [{"x": 5, "y": 5}]}
        result = transition_state(mob, "patrol", data)
        assert result is True
        assert mob.behavior_data == data

    def test_valid_transition_clears_data(self, mob):
        """transition_state clears data if None provided."""
        mob.behavior_data = {"old": "data"}
        result = transition_state(mob, "idle", None)
        assert result is True
        assert mob.behavior_data == {}

    def test_invalid_state_returns_false(self, mob):
        """transition_state with invalid state returns False."""
        result = transition_state(mob, "invalid_state")
        assert result is False

    def test_invalid_state_no_change(self, mob):
        """transition_state with invalid state doesn't change mob."""
        original_state = mob.behavior_state
        original_data = mob.behavior_data
        transition_state(mob, "invalid_state")
        assert mob.behavior_state == original_state
        assert mob.behavior_data == original_data

    def test_all_valid_states_accepted(self, mob):
        """All states in VALID_STATES can be transitioned to."""
        for state in VALID_STATES:
            result = transition_state(mob, state)
            assert result is True
            assert mob.behavior_state == state


class TestDefaultState:
    def test_new_mob_starts_idle(self):
        """New Mob instance defaults to idle state."""
        mob = Mob(name="new mob", x=0, y=0)
        assert mob.behavior_state == "idle"
        assert mob.behavior_data == {}


class TestPatrolMovement:
    @pytest.mark.asyncio
    async def test_patrol_moves_toward_waypoint(self, mob, test_zone):
        """Mob on patrol moves toward current waypoint."""
        waypoints = [{"x": 15, "y": 10}]
        transition_state(mob, "patrol", {"waypoints": waypoints, "waypoint_index": 0})

        # process_behavior should determine direction but not move
        # (movement is handled by mob_ai integration later)
        direction = get_patrol_direction(mob, test_zone)
        assert direction == "east"  # mob at (10, 10) moves east to (15, 10)

    @pytest.mark.asyncio
    async def test_patrol_advances_waypoint(self, mob, test_zone):
        """Mob reaching waypoint advances to next."""
        waypoints = [{"x": 10, "y": 10}, {"x": 15, "y": 15}]
        transition_state(mob, "patrol", {"waypoints": waypoints, "waypoint_index": 0})

        # At first waypoint already
        direction = get_patrol_direction(mob, test_zone)
        assert direction is None  # at waypoint
        # Process behavior to advance
        await process_behavior(mob, test_zone)
        assert mob.behavior_data["waypoint_index"] == 1

    @pytest.mark.asyncio
    async def test_patrol_loops_waypoints(self, mob, test_zone):
        """Patrol loops back to first waypoint after last."""
        waypoints = [{"x": 5, "y": 5}, {"x": 10, "y": 10}]
        transition_state(mob, "patrol", {"waypoints": waypoints, "waypoint_index": 1})

        # At second waypoint (last one)
        direction = get_patrol_direction(mob, test_zone)
        assert direction is None
        await process_behavior(mob, test_zone)
        assert mob.behavior_data["waypoint_index"] == 0  # looped back

    def test_patrol_direction_north(self, mob, test_zone):
        """get_patrol_direction returns 'north' when waypoint is north."""
        waypoints = [{"x": 10, "y": 5}]
        transition_state(mob, "patrol", {"waypoints": waypoints, "waypoint_index": 0})
        direction = get_patrol_direction(mob, test_zone)
        assert direction == "north"

    def test_patrol_direction_south(self, mob, test_zone):
        """get_patrol_direction returns 'south' when waypoint is south."""
        waypoints = [{"x": 10, "y": 15}]
        transition_state(mob, "patrol", {"waypoints": waypoints, "waypoint_index": 0})
        direction = get_patrol_direction(mob, test_zone)
        assert direction == "south"

    def test_patrol_direction_west(self, mob, test_zone):
        """get_patrol_direction returns 'west' when waypoint is west."""
        waypoints = [{"x": 5, "y": 10}]
        transition_state(mob, "patrol", {"waypoints": waypoints, "waypoint_index": 0})
        direction = get_patrol_direction(mob, test_zone)
        assert direction == "west"

    def test_patrol_direction_with_wrapping(self, mob, test_zone):
        """get_patrol_direction handles toroidal wrapping."""
        # Mob at (10, 10), waypoint at (250, 10)
        # Direct path: 240 tiles east
        # Wrapped path: 20 tiles west (256 - 240 + 10)
        waypoints = [{"x": 250, "y": 10}]
        transition_state(mob, "patrol", {"waypoints": waypoints, "waypoint_index": 0})
        direction = get_patrol_direction(mob, test_zone)
        assert direction == "west"  # wrapping is shorter


class TestConverseState:
    @pytest.mark.asyncio
    async def test_converse_state_no_movement(self, mob, test_zone):
        """Mob in converse state doesn't move."""
        original_x, original_y = mob.x, mob.y
        transition_state(mob, "converse")
        await process_behavior(mob, test_zone)
        assert mob.x == original_x
        assert mob.y == original_y


class TestFleeState:
    def test_flee_direction_west(self, mob, test_zone):
        """get_flee_direction returns 'west' when fleeing from threat to the east."""
        # Threat at (15, 10), mob at (10, 10)
        # Should flee west (away from threat)
        transition_state(mob, "flee", {"flee_from": {"x": 15, "y": 10}})
        direction = get_flee_direction(mob, test_zone)
        assert direction == "west"

    def test_flee_direction_east(self, mob, test_zone):
        """get_flee_direction returns 'east' when fleeing from threat to the west."""
        # Threat at (5, 10), mob at (10, 10)
        # Should flee east (away from threat)
        transition_state(mob, "flee", {"flee_from": {"x": 5, "y": 10}})
        direction = get_flee_direction(mob, test_zone)
        assert direction == "east"

    def test_flee_direction_north(self, mob, test_zone):
        """get_flee_direction returns 'north' when fleeing from threat to the south."""
        # Threat at (10, 15), mob at (10, 10)
        # Should flee north (away from threat)
        transition_state(mob, "flee", {"flee_from": {"x": 10, "y": 15}})
        direction = get_flee_direction(mob, test_zone)
        assert direction == "north"

    def test_flee_direction_south(self, mob, test_zone):
        """get_flee_direction returns 'south' when fleeing from threat to the north."""
        # Threat at (10, 5), mob at (10, 10)
        # Should flee south (away from threat)
        transition_state(mob, "flee", {"flee_from": {"x": 10, "y": 5}})
        direction = get_flee_direction(mob, test_zone)
        assert direction == "south"

    def test_flee_no_threat(self, mob, test_zone):
        """get_flee_direction returns None when no threat specified."""
        transition_state(mob, "flee", {})
        direction = get_flee_direction(mob, test_zone)
        assert direction is None


class TestWorkingState:
    @pytest.mark.asyncio
    async def test_working_state_no_movement(self, mob, test_zone):
        """Mob in working state stays put."""
        original_x, original_y = mob.x, mob.y
        transition_state(mob, "working")
        await process_behavior(mob, test_zone)
        assert mob.x == original_x
        assert mob.y == original_y


class TestIdleState:
    @pytest.mark.asyncio
    async def test_idle_state_no_action(self, mob, test_zone):
        """Idle state does nothing (movement handled by mob_ai)."""
        original_x, original_y = mob.x, mob.y
        transition_state(mob, "idle")
        await process_behavior(mob, test_zone)
        # process_behavior is a no-op for idle
        assert mob.x == original_x
        assert mob.y == original_y
