"""Tests for prefix matching in command dispatch."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib import commands
from mudlib.commands import CommandDefinition


@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,
    )


async def dummy_handler(player, args):
    """A simple handler for testing."""
    player.writer.write(f"Handler called with args: {args}\r\n")
    await player.writer.drain()


@pytest.fixture
def clean_registry():
    """Clear registry before each test."""
    original_registry = commands._registry.copy()
    commands._registry.clear()
    yield
    commands._registry.clear()
    commands._registry.update(original_registry)


@pytest.mark.asyncio
async def test_exact_match_still_works(player, clean_registry):
    """Test that exact matches work as before."""
    commands.register(
        CommandDefinition(name="look", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "look")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "Handler called" in output
    assert "Unknown command" not in output


@pytest.mark.asyncio
async def test_prefix_match_resolves_unique_prefix(player, clean_registry):
    """Test that a unique prefix resolves to the full command."""
    commands.register(
        CommandDefinition(name="sweep", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "swe")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "Handler called" in output
    assert "Unknown command" not in output


@pytest.mark.asyncio
async def test_ambiguous_prefix_two_matches(player, clean_registry):
    """Test that ambiguous prefix with 2 matches shows 'A or B?' message."""
    commands.register(
        CommandDefinition(name="swoop", handler=dummy_handler, mode="normal")
    )
    commands.register(
        CommandDefinition(name="swallow", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "sw")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Check for either order
    assert "swallow or swoop?" in output or "swoop or swallow?" in output
    assert "Handler called" not in output


@pytest.mark.asyncio
async def test_ambiguous_prefix_three_plus_matches(player, clean_registry):
    """Test that ambiguous prefix with 3+ matches shows comma-separated."""
    commands.register(
        CommandDefinition(name="send", handler=dummy_handler, mode="normal")
    )
    commands.register(
        CommandDefinition(name="set", handler=dummy_handler, mode="normal")
    )
    commands.register(
        CommandDefinition(name="settings", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "se")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Should contain all three with commas and "or"
    assert "send" in output
    assert "set" in output
    assert "settings" in output
    assert ", or " in output
    assert "Handler called" not in output


@pytest.mark.asyncio
async def test_no_match_shows_unknown_command(player, clean_registry):
    """Test that no match shows unknown command message."""
    commands.register(
        CommandDefinition(name="look", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "xyz")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "Unknown command: xyz" in output
    assert "Handler called" not in output


@pytest.mark.asyncio
async def test_exact_alias_match_wins_over_prefix(player, clean_registry):
    """Test that exact match on alias takes priority over prefix match."""
    commands.register(
        CommandDefinition(
            name="southwest",
            aliases=["sw"],
            handler=dummy_handler,
            mode="normal",
        )
    )
    commands.register(
        CommandDefinition(name="sweep", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "sw")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Should call handler (exact match on alias), not show ambiguous
    assert "Handler called" in output
    assert "or" not in output


@pytest.mark.asyncio
async def test_single_char_ambiguous_prefix(player, clean_registry):
    """Test that single-char ambiguous prefix shows disambiguation."""
    commands.register(
        CommandDefinition(name="quit", handler=dummy_handler, mode="normal")
    )
    commands.register(
        CommandDefinition(name="query", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "q")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "quit or query?" in output or "query or quit?" in output
    assert "Handler called" not in output


@pytest.mark.asyncio
async def test_prefix_match_deduplicates_aliases(player, clean_registry):
    """Test that aliases to the same command don't create multiple matches."""
    commands.register(
        CommandDefinition(
            name="long",
            aliases=["l", "lon"],
            handler=dummy_handler,
            mode="normal",
        )
    )
    commands.register(
        CommandDefinition(name="lock", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "lo")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Should show just "lock or long?" not multiple entries for long's aliases
    assert "lock" in output
    assert "long" in output
    # Should not list long multiple times
    output_lower = output.lower()
    # Count occurrences of the word "long" (not as substring like "lock")
    # Looking for "long" as a standalone word in disambiguation
    assert output_lower.count("long") == 1


@pytest.mark.asyncio
async def test_prefix_match_with_args(player, clean_registry):
    """Test that prefix matching preserves arguments."""
    commands.register(
        CommandDefinition(name="sweep", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "swe the floor")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    assert "Handler called with args: the floor" in output


@pytest.mark.asyncio
async def test_alias_exact_match_over_prefix_collision(player, clean_registry):
    """Test alias exact match wins over prefix when otherwise ambiguous."""
    commands.register(
        CommandDefinition(
            name="look", aliases=["l"], handler=dummy_handler, mode="normal"
        )
    )
    commands.register(
        CommandDefinition(name="list", handler=dummy_handler, mode="normal")
    )

    await commands.dispatch(player, "l")
    output = "".join([call[0][0] for call in player.writer.write.call_args_list])

    # Should call handler (exact match on alias), not show ambiguous
    assert "Handler called" in output
    assert "or" not in output
