"""Tests for the look command with structured room display."""

import time
from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands import look  # noqa: F401
from mudlib.commands.look import cmd_look
from mudlib.entity import Mob
from mudlib.gametime import init_game_time
from mudlib.player import Player
from mudlib.portal import Portal
from mudlib.thing import Thing
from mudlib.weather import WeatherCondition, init_weather
from mudlib.zone import Zone


@pytest.fixture(autouse=True)
def _reset_globals():
    yield
    import mudlib.gametime
    import mudlib.weather

    mudlib.gametime._game_time = None
    mudlib.weather._current_weather = None


@pytest.fixture
def mock_writer():
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    return writer


@pytest.fixture
def mock_reader():
    return MagicMock()


@pytest.fixture
def test_zone():
    """Create a test zone with simple terrain."""
    # 50x50 zone with grass everywhere
    terrain = [["." for _ in range(50)] for _ in range(50)]
    # Add some mountains
    terrain[10][10] = "^"
    terrain[10][11] = "^"
    return Zone(
        name="test_zone",
        description="The Test Zone",
        width=50,
        height=50,
        terrain=terrain,
        impassable={"^", "~"},
    )


@pytest.fixture
def player(mock_reader, mock_writer, test_zone):
    """Create a test player in the test zone."""
    p = Player(
        name="TestPlayer",
        x=25,
        y=25,
        reader=mock_reader,
        writer=mock_writer,
    )
    p.location = test_zone
    test_zone._contents.append(p)
    return p


def get_output(player):
    """Get all output written to player's writer."""
    return "".join([call[0][0] for call in player.writer.write.call_args_list])


@pytest.mark.asyncio
async def test_look_includes_where_header(player):
    """Look output should include 'Where: {zone description}' line."""
    await cmd_look(player, "")
    output = get_output(player)
    assert "Where: The Test Zone" in output


@pytest.mark.asyncio
async def test_look_includes_location_line(player):
    """Look output should include 'Location:' line with quadrant and coords."""
    await cmd_look(player, "")
    output = get_output(player)
    assert "Location: center 25, 25" in output


@pytest.mark.asyncio
async def test_look_includes_exits_line(player):
    """Look output should include 'Exits:' line."""
    await cmd_look(player, "")
    output = get_output(player)
    assert "Exits: north south east west" in output


@pytest.mark.asyncio
async def test_look_shows_nearby_entities(player, test_zone):
    """Look should show nearby entities (not on player's tile) in viewport."""
    # Add some mobs in viewport range (location param auto-adds to zone)
    Mob(name="Goku", x=26, y=25, location=test_zone)
    Mob(name="Vegeta", x=27, y=26, location=test_zone)

    await cmd_look(player, "")
    output = get_output(player)

    # Should see nearby line with count
    assert "Nearby: (2)" in output
    assert "Goku" in output
    assert "Vegeta" in output


@pytest.mark.asyncio
async def test_look_shows_entities_here(player, test_zone):
    """Look should show entities on player's tile with posture."""
    # Add entities on player's tile (location param auto-adds to zone)
    Mob(name="Krillin", x=25, y=25, resting=True, location=test_zone)
    Mob(name="Piccolo", x=25, y=25, location=test_zone)

    await cmd_look(player, "")
    output = get_output(player)

    # Should see individual lines with postures
    assert "Krillin is resting here." in output
    assert "Piccolo is standing here." in output


@pytest.mark.asyncio
async def test_look_shows_ground_items(player, test_zone):
    """Look should show items on the ground."""
    # Add an item on player's tile (location param auto-adds to zone)
    Thing(name="rusty sword", x=25, y=25, location=test_zone)

    await cmd_look(player, "")
    output = get_output(player)

    # Should see ground items
    assert "rusty sword" in output


@pytest.mark.asyncio
async def test_look_shows_portals(player, test_zone):
    """Look should show portals on player's tile."""
    # Add a portal on player's tile (location param auto-adds to zone)
    Portal(
        name="wide dirt path",
        x=25,
        y=25,
        location=test_zone,
        target_zone="other",
        target_x=0,
        target_y=0,
    )

    await cmd_look(player, "")
    output = get_output(player)

    # Should see portal with new format
    assert "You see wide dirt path." in output


@pytest.mark.asyncio
async def test_look_with_args_routes_to_examine(player, test_zone):
    """look <thing> should route to examine command logic."""
    # Add an item to examine (location param auto-adds to zone)
    Thing(name="sword", x=25, y=25, description="A sharp blade.", location=test_zone)

    await cmd_look(player, "sword")
    output = get_output(player)

    # Should see the item's description (examine behavior)
    assert "A sharp blade." in output


@pytest.mark.asyncio
async def test_look_structure_order(player, test_zone):
    """Look output should have sections in correct order."""
    # Add entities and items (location param auto-adds to zone)
    Mob(name="Goku", x=25, y=25, location=test_zone)

    await cmd_look(player, "")
    output = get_output(player)

    # Find positions of key sections
    where_pos = output.find("Where:")
    location_pos = output.find("Location:")
    exits_pos = output.find("Exits:")

    # Verify order: Where comes first, then viewport (with terrain),
    # then Location, then Exits
    assert where_pos < location_pos
    assert location_pos < exits_pos


@pytest.mark.asyncio
async def test_look_nowhere(mock_reader, mock_writer):
    """Look should show 'You are nowhere.' when player has no location."""
    # Create player without a location
    player = Player(
        name="NowherePlayer",
        x=0,
        y=0,
        reader=mock_reader,
        writer=mock_writer,
    )
    player.location = None

    await cmd_look(player, "")
    output = get_output(player)

    assert "You are nowhere." in output


@pytest.mark.asyncio
async def test_look_includes_atmosphere_clear():
    """Look should include atmosphere line with clear weather."""
    # Initialize game time and weather
    init_game_time(epoch=time.time(), real_minutes_per_game_hour=1.0)
    init_weather(condition=WeatherCondition.clear, intensity=0.5)

    # Create player
    mock_reader = MagicMock()
    mock_writer = MagicMock()
    mock_writer.write = MagicMock()
    mock_writer.drain = AsyncMock()

    terrain = [["." for _ in range(50)] for _ in range(50)]
    zone = Zone(
        name="test_zone",
        description="The Test Zone",
        width=50,
        height=50,
        terrain=terrain,
        impassable={"^", "~"},
    )

    player = Player(
        name="TestPlayer", x=25, y=25, reader=mock_reader, writer=mock_writer
    )
    player.location = zone
    zone._contents.append(player)

    await cmd_look(player, "")
    output = get_output(player)

    # Should see atmosphere line with period and season
    assert "[" in output and "]" in output
    # Should not have weather description for clear weather
    # (the format is: sky. [period, season])
    seasons = ["spring]", "summer]", "autumn]", "winter]"]
    assert any(season in output for season in seasons)


@pytest.mark.asyncio
async def test_look_includes_atmosphere_with_rain():
    """Look should include atmosphere line with rain."""
    # Initialize game time and weather
    init_game_time(epoch=time.time(), real_minutes_per_game_hour=1.0)
    init_weather(condition=WeatherCondition.rain, intensity=0.5)

    # Create player
    mock_reader = MagicMock()
    mock_writer = MagicMock()
    mock_writer.write = MagicMock()
    mock_writer.drain = AsyncMock()

    terrain = [["." for _ in range(50)] for _ in range(50)]
    zone = Zone(
        name="test_zone",
        description="The Test Zone",
        width=50,
        height=50,
        terrain=terrain,
        impassable={"^", "~"},
    )

    player = Player(
        name="TestPlayer", x=25, y=25, reader=mock_reader, writer=mock_writer
    )
    player.location = zone
    zone._contents.append(player)

    await cmd_look(player, "")
    output = get_output(player)

    # Should see atmosphere line with rain description
    assert "rain" in output.lower()
    # Should see season tag
    assert "[" in output and "]" in output


@pytest.mark.asyncio
async def test_look_atmosphere_between_where_and_viewport():
    """Atmosphere line should appear between Where header and viewport."""
    # Initialize game time and weather
    init_game_time(epoch=time.time(), real_minutes_per_game_hour=1.0)
    init_weather(condition=WeatherCondition.clear, intensity=0.5)

    # Create player
    mock_reader = MagicMock()
    mock_writer = MagicMock()
    mock_writer.write = MagicMock()
    mock_writer.drain = AsyncMock()

    terrain = [["." for _ in range(50)] for _ in range(50)]
    zone = Zone(
        name="test_zone",
        description="The Test Zone",
        width=50,
        height=50,
        terrain=terrain,
        impassable={"^", "~"},
    )

    player = Player(
        name="TestPlayer", x=25, y=25, reader=mock_reader, writer=mock_writer
    )
    player.location = zone
    zone._contents.append(player)

    await cmd_look(player, "")
    output = get_output(player)

    # Find positions of key sections
    where_pos = output.find("Where:")
    location_pos = output.find("Location:")

    # Atmosphere should be between Where and Location
    # Look for a bracket tag (season indicator)
    bracket_pos = output.find("[")

    assert where_pos < bracket_pos < location_pos


@pytest.mark.asyncio
async def test_look_night_reduces_viewport():
    """Look at night should show smaller viewport than day."""

    # Set time to noon (hour 12) - epoch was 12 hours ago
    # elapsed_game_hours = (time.time() - epoch) / 60 / 1.0 = 12
    # so epoch = time.time() - 12 * 60
    init_game_time(epoch=time.time() - 12 * 60, real_minutes_per_game_hour=1.0)
    init_weather(condition=WeatherCondition.clear, intensity=0.5)

    mock_reader = MagicMock()
    mock_writer = MagicMock()
    mock_writer.write = MagicMock()
    mock_writer.drain = AsyncMock()

    terrain = [["." for _ in range(50)] for _ in range(50)]
    zone = Zone(
        name="test_zone",
        description="The Test Zone",
        width=50,
        height=50,
        terrain=terrain,
        impassable={"^", "~"},
    )

    player = Player(
        name="TestPlayer", x=25, y=25, reader=mock_reader, writer=mock_writer
    )
    player.location = zone
    zone._contents.append(player)

    await cmd_look(player, "")
    day_output = get_output(player)

    # Find viewport section (between atmosphere and Location:)
    day_lines = day_output.split("\r\n")
    # Viewport is after atmosphere (containing '[') and before Location:
    viewport_start_idx = None
    viewport_end_idx = None
    for i, line in enumerate(day_lines):
        if "[" in line and viewport_start_idx is None:
            viewport_start_idx = i + 1
        if line.startswith("Location:"):
            viewport_end_idx = i
            break

    assert viewport_start_idx is not None
    assert viewport_end_idx is not None
    day_viewport_height = viewport_end_idx - viewport_start_idx

    # Reset writer
    mock_writer.reset_mock()

    # Set time to night (hour 22) - epoch was 22 hours ago
    init_game_time(epoch=time.time() - 22 * 60, real_minutes_per_game_hour=1.0)

    await cmd_look(player, "")
    night_output = get_output(player)

    # Find viewport section
    night_lines = night_output.split("\r\n")
    viewport_start_idx = None
    viewport_end_idx = None
    for i, line in enumerate(night_lines):
        if "[" in line and viewport_start_idx is None:
            viewport_start_idx = i + 1
        if line.startswith("Location:"):
            viewport_end_idx = i
            break

    assert viewport_start_idx is not None
    assert viewport_end_idx is not None
    night_viewport_height = viewport_end_idx - viewport_start_idx

    # Night should have fewer viewport lines (9 vs 11)
    assert night_viewport_height < day_viewport_height
    assert day_viewport_height == 11  # full viewport at noon
    assert night_viewport_height == 9  # reduced viewport at night
