"""Tests for the commands listing command."""

from pathlib import Path
from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib import commands

# Import command modules to register their commands
from mudlib.commands import (
    edit,  # noqa: F401
    fly,  # noqa: F401
    help,  # noqa: F401
    look,  # noqa: F401
    movement,  # noqa: F401
    quit,  # noqa: F401
)


@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 player(mock_reader, mock_writer):
    from mudlib.player import Player

    return Player(
        name="TestPlayer",
        x=5,
        y=5,
        reader=mock_reader,
        writer=mock_writer,
    )


@pytest.fixture
def combat_moves():
    """Load and register combat moves from content directory."""
    from mudlib.combat.commands import register_combat_commands

    combat_dir = Path(__file__).resolve().parents[1] / "content" / "combat"
    register_combat_commands(combat_dir)

    from mudlib.combat import commands as combat_cmds

    yield combat_cmds.combat_moves

    # Clean up
    combat_cmds.combat_moves = {}
    # Note: commands stay registered in _registry, but that's ok for tests


@pytest.mark.asyncio
async def test_commands_command_exists():
    """Test that commands command is registered."""
    assert "commands" in commands._registry
    assert "cmds" in commands._registry
    assert commands._registry["commands"] is commands._registry["cmds"]


@pytest.mark.asyncio
async def test_commands_has_wildcard_mode():
    """Test that commands works from any mode."""
    assert commands._registry["commands"].mode == "*"


@pytest.mark.asyncio
async def test_commands_lists_all_commands(player):
    """Test that commands command lists all unique commands."""
    # Execute the command
    await commands.dispatch(player, "commands")

    # Collect all output
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Should show some specific commands with their primary names
    assert "north" in output
    assert "look" in output
    assert "quit" in output
    assert "commands" in output

    # Should not contain combat moves
    assert "punch" not in output
    assert "roundhouse" not in output


@pytest.mark.asyncio
async def test_commands_clean_names_no_parens(player):
    """Test that command names are shown cleanly without alias parens."""
    await commands.dispatch(player, "commands")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Commands should appear without parens
    assert "north" in output
    assert "look" in output
    assert "quit" in output
    assert "commands" in output

    # Should NOT show aliases in parens
    assert "north(n)" not in output
    assert "look(l)" not in output
    assert "quit(q)" not in output
    assert "commands(cmds)" not in output


@pytest.mark.asyncio
async def test_commands_deduplicates_entries(player):
    """Test that aliases don't create duplicate command entries."""
    await commands.dispatch(player, "commands")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Count how many times "north" appears as a primary command
    # It should appear once in the movement group, not multiple times
    # (Note: it might appear in parens as part of another command, but
    # we're checking it doesn't appear as a standalone entry multiple times)
    lines_with_north_command = [
        line for line in output.split("\n") if line.strip().startswith("north")
    ]
    # Should only have one line starting with "north" as a command
    assert len(lines_with_north_command) <= 1


@pytest.mark.asyncio
async def test_commands_sorted_alphabetically(player):
    """Test that commands are sorted alphabetically."""
    await commands.dispatch(player, "commands")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # All commands should appear in a single sorted list
    assert "north" in output
    assert "east" in output
    assert "quit" in output
    assert "look" in output


@pytest.mark.asyncio
async def test_commands_via_alias(player):
    """Test that the cmds alias works."""
    await commands.dispatch(player, "cmds")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Should produce the same output as commands
    assert "north" in output
    assert "look" in output


@pytest.mark.asyncio
async def test_commands_detail_regular_command(player):
    """Test commands detail view for a regular command."""
    await commands.dispatch(player, "commands look")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "look" in output
    assert "aliases: l" in output
    assert "mode: normal" in output


@pytest.mark.asyncio
async def test_commands_detail_simple_combat_move(player, combat_moves):
    """Test commands detail view for a simple combat move."""
    await commands.dispatch(player, "commands roundhouse")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "roundhouse" in output
    assert "type: attack" in output
    assert "stamina: 8.0" in output
    assert "timing window: 2000ms" in output
    assert "damage: 25%" in output
    assert "{attacker} shifts {his} weight back..." in output
    assert "countered by: duck, parry high, parry low" in output


@pytest.mark.asyncio
async def test_commands_detail_variant_base(player, combat_moves):
    """Test commands detail view for a variant base command."""
    await commands.dispatch(player, "commands punch")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "punch" in output
    assert "type: attack" in output

    # Should show both variants
    assert "punch left" in output
    assert "{attacker} retracts {his} left arm..." in output
    assert "countered by: dodge right, parry high" in output

    assert "punch right" in output
    assert "{attacker} retracts {his} right arm..." in output
    assert "countered by: dodge left, parry high" in output

    # Should show shared properties in each variant
    assert "stamina: 5.0" in output
    assert "timing window: 1800ms" in output
    assert "damage: 15%" in output


@pytest.mark.asyncio
async def test_commands_detail_specific_variant(player, combat_moves):
    """Test commands detail view for a specific variant."""
    await commands.dispatch(player, "commands punch left")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "punch left" in output
    assert "type: attack" in output
    assert "stamina: 5.0" in output
    assert "timing window: 1800ms" in output
    assert "damage: 15%" in output
    assert "{attacker} retracts {his} left arm..." in output
    assert "countered by: dodge right, parry high" in output

    # Should NOT show "punch right"
    assert "punch right" not in output


@pytest.mark.asyncio
async def test_commands_detail_unknown_command(player):
    """Test commands detail view for an unknown command."""
    await commands.dispatch(player, "commands nonexistent")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "Unknown command: nonexistent" in output


@pytest.mark.asyncio
async def test_commands_detail_via_prefix(player, combat_moves):
    """Test commands detail view via prefix matching."""
    await commands.dispatch(player, "commands ro")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Should show the full command details via prefix match
    assert "roundhouse" in output
    assert "type: attack" in output
