"""Tests for the play command."""

from unittest.mock import AsyncMock, MagicMock, Mock, patch

import pytest

from mudlib.zone import Zone


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


@pytest.fixture
def test_zone():
    """Create a test zone for spatial queries."""
    terrain = [["."] * 20 for _ in range(20)]
    return Zone(name="test", width=20, height=20, terrain=terrain, toroidal=False)


@pytest.fixture
def player(mock_writer, test_zone):
    from mudlib.player import Player

    return Player(name="tester", location=test_zone, x=5, y=5, writer=mock_writer)


def test_play_command_registered():
    """Verify play command is registered."""
    import mudlib.commands.play  # noqa: F401
    from mudlib import commands

    assert "play" in commands._registry
    cmd = commands._registry["play"]
    assert cmd.name == "play"
    assert cmd.mode == "normal"


@pytest.mark.asyncio
async def test_play_no_args(player):
    """Playing with no args sends usage message."""
    from mudlib.commands.play import cmd_play

    await cmd_play(player, "")
    player.writer.write.assert_called()
    output = player.writer.write.call_args[0][0]
    assert "play what?" in output.lower()


@pytest.mark.asyncio
async def test_play_unknown_story(player):
    """Playing unknown story sends error message."""
    from mudlib.commands.play import cmd_play

    await cmd_play(player, "nosuchgame")
    player.writer.write.assert_called()
    output = player.writer.write.call_args[0][0]
    assert "no story" in output.lower()
    assert "nosuchgame" in output.lower()


@pytest.mark.asyncio
async def test_play_enters_if_mode(player):
    """Playing a valid story enters IF mode and creates session."""
    from pathlib import Path

    from mudlib.commands.play import cmd_play

    # Mock IFSession
    mock_session = Mock()
    mock_session.start = AsyncMock(return_value="Welcome to Zork!")
    mock_session.save_path = Mock(spec=Path)
    mock_session.save_path.exists = Mock(return_value=False)

    with patch("mudlib.commands.play.EmbeddedIFSession") as MockSession:
        MockSession.return_value = mock_session

        with patch("mudlib.commands.play._find_story") as mock_find:
            mock_find.return_value = "/fake/path/zork1.z5"

            await cmd_play(player, "zork1")

    # Verify session was created and started
    mock_session.start.assert_called_once()

    # Verify mode was pushed
    assert "if" in player.mode_stack

    # Verify session was attached to player
    assert player.if_session is mock_session

    # Verify intro was sent
    player.writer.write.assert_called()
    output = player.writer.write.call_args[0][0]
    assert "Welcome to Zork!" in output


@pytest.mark.asyncio
async def test_play_handles_dfrotz_missing(player):
    """Playing when dfrotz is missing sends error."""
    from pathlib import Path

    from mudlib.commands.play import cmd_play

    # Mock IFSession to raise FileNotFoundError on start
    mock_session = Mock()
    mock_session.start = AsyncMock(side_effect=FileNotFoundError())
    mock_session.stop = AsyncMock()
    mock_session.save_path = Mock(spec=Path)
    mock_session.save_path.exists = Mock(return_value=False)

    with patch("mudlib.commands.play.IFSession") as MockIFSession:
        MockIFSession.return_value = mock_session

        # Use .zblorb to test dfrotz path (z3/z5/z8 go to embedded)
        with patch("mudlib.commands.play._find_story") as mock_find:
            mock_find.return_value = "/fake/path/game.zblorb"

            await cmd_play(player, "game")

    # Verify error message was sent
    player.writer.write.assert_called()
    output = player.writer.write.call_args[0][0]
    assert "dfrotz not found" in output.lower()

    # Verify mode was NOT pushed
    assert "if" not in player.mode_stack

    # Verify session was NOT attached
    assert player.if_session is None

    # Verify session.stop() was called
    mock_session.stop.assert_called_once()


@pytest.mark.asyncio
async def test_play_restores_save_if_exists(player):
    """Playing restores saved game if save file exists (via start())."""
    from mudlib.commands.play import cmd_play

    # Mock IFSession - restore now happens in start() before thread launches
    mock_session = Mock()
    restored_output = (
        "restoring saved game...\r\nrestored.\r\n\r\n"
        "West of House\nYou are standing in an open field."
    )
    mock_session.start = AsyncMock(return_value=restored_output)

    with patch("mudlib.commands.play.EmbeddedIFSession") as MockSession:
        MockSession.return_value = mock_session

        with patch("mudlib.commands.play._find_story") as mock_find:
            mock_find.return_value = "/fake/path/zork1.z5"

            await cmd_play(player, "zork1")

    # Verify session was created and started
    mock_session.start.assert_called_once()

    # Verify mode was pushed
    assert "if" in player.mode_stack

    # Verify restored text was sent (start() returns full output with restore)
    calls = [call[0][0] for call in player.writer.write.call_args_list]
    full_output = "".join(calls)
    assert "West of House" in full_output
    assert "open field" in full_output


@pytest.mark.asyncio
async def test_play_no_restore_if_no_save(player):
    """Playing does not restore if no save file exists."""

    from mudlib.commands.play import cmd_play

    # Mock EmbeddedIFSession
    mock_session = Mock()
    mock_session.start = AsyncMock(return_value="Welcome to Zork!")

    with patch("mudlib.commands.play.EmbeddedIFSession") as MockSession:
        MockSession.return_value = mock_session

        with patch("mudlib.commands.play._find_story") as mock_find:
            mock_find.return_value = "/fake/path/zork1.z5"

            await cmd_play(player, "zork1")

    # Verify session was created and started
    mock_session.start.assert_called_once()

    # Verify intro was sent but not restore message
    calls = [call[0][0] for call in player.writer.write.call_args_list]
    full_output = "".join(calls)
    assert "Welcome to Zork!" in full_output
    assert "restoring" not in full_output.lower()
