"""Tests for variant prefix matching in combat commands."""

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

import pytest

from mudlib.combat import commands as combat_commands
from mudlib.combat.engine import active_encounters
from mudlib.combat.moves import load_moves
from mudlib.player import Player, players
from mudlib.zone import Zone


@pytest.fixture(autouse=True)
def clear_state():
    """Clear encounters and players before and after each test."""
    active_encounters.clear()
    players.clear()
    yield
    active_encounters.clear()
    players.clear()


@pytest.fixture
def test_zone():
    """Create a test zone for players."""
    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 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, test_zone):
    p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
    p.location = test_zone
    test_zone._contents.append(p)
    players[p.name] = p
    return p


@pytest.fixture
def target(mock_reader, mock_writer, test_zone):
    t = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
    t.location = test_zone
    test_zone._contents.append(t)
    players[t.name] = t
    return t


@pytest.fixture
def moves():
    """Load combat moves from content directory."""
    content_dir = Path(__file__).parent.parent / "content" / "combat"
    return load_moves(content_dir)


# --- variant prefix matching tests ---


@pytest.mark.asyncio
async def test_exact_match_still_works(player, target, moves):
    """Test that exact variant names still work (e.g., 'high' matches 'high')."""
    variant_moves = {
        "high": moves["parry high"],
        "low": moves["parry low"],
    }
    handler = combat_commands.make_variant_handler(
        "parry", variant_moves, combat_commands.do_defend
    )

    await handler(player, "high")

    # Should succeed without error
    messages = [call[0][0] for call in player.writer.write.call_args_list]
    assert not any("unknown" in msg.lower() for msg in messages)
    assert not any("ambiguous" in msg.lower() for msg in messages)


@pytest.mark.asyncio
async def test_prefix_match_works(player, target, moves):
    """Test that prefix matching resolves unique prefixes (e.g., 'hi' -> 'high')."""
    variant_moves = {
        "high": moves["parry high"],
        "low": moves["parry low"],
    }
    handler = combat_commands.make_variant_handler(
        "parry", variant_moves, combat_commands.do_defend
    )

    # Test "hi" -> "high"
    player.writer.write.reset_mock()
    await handler(player, "hi")
    messages = [call[0][0] for call in player.writer.write.call_args_list]
    assert not any("unknown" in msg.lower() for msg in messages)
    assert not any("ambiguous" in msg.lower() for msg in messages)

    # Test "lo" -> "low"
    player.writer.write.reset_mock()
    await handler(player, "lo")
    messages = [call[0][0] for call in player.writer.write.call_args_list]
    assert not any("unknown" in msg.lower() for msg in messages)
    assert not any("ambiguous" in msg.lower() for msg in messages)


@pytest.mark.asyncio
async def test_ambiguous_prefix_shows_disambiguation(player, moves):
    """Test that ambiguous prefix shows options (e.g., 'l' for 'left'/'long')."""
    # Create hypothetical moves with conflicting prefixes
    variant_moves = {
        "left": moves["punch left"],
        "long": moves["punch left"],  # reuse same move, just for testing keys
    }
    handler = combat_commands.make_variant_handler(
        "punch", variant_moves, combat_commands.do_attack
    )

    await handler(player, "l")

    messages = [call[0][0] for call in player.writer.write.call_args_list]
    output = "".join(messages).lower()
    assert "ambiguous" in output
    assert "left" in output
    assert "long" in output


@pytest.mark.asyncio
async def test_no_match_shows_error(player, moves):
    """Test that no matching variant shows error with valid options."""
    variant_moves = {
        "high": moves["parry high"],
        "low": moves["parry low"],
    }
    handler = combat_commands.make_variant_handler(
        "parry", variant_moves, combat_commands.do_defend
    )

    await handler(player, "middle")

    messages = [call[0][0] for call in player.writer.write.call_args_list]
    output = "".join(messages).lower()
    assert "unknown" in output
    assert "high" in output or "low" in output


@pytest.mark.asyncio
async def test_prefix_with_target_args(player, target, moves):
    """Test that prefix matching preserves target arguments."""
    variant_moves = {
        "left": moves["punch left"],
        "right": moves["punch right"],
    }
    handler = combat_commands.make_variant_handler(
        "punch", variant_moves, combat_commands.do_attack
    )

    await handler(player, "le Vegeta")

    # Should start combat with target
    from mudlib.combat.engine import get_encounter

    encounter = get_encounter(player)
    assert encounter is not None
    assert encounter.defender is target


@pytest.mark.asyncio
async def test_single_char_prefix_match(player, moves):
    """Test that single-char prefix works when unambiguous (e.g., 'h' -> 'high')."""
    variant_moves = {
        "high": moves["parry high"],
        "low": moves["parry low"],
    }
    handler = combat_commands.make_variant_handler(
        "parry", variant_moves, combat_commands.do_defend
    )

    await handler(player, "h")

    messages = [call[0][0] for call in player.writer.write.call_args_list]
    assert not any("unknown" in msg.lower() for msg in messages)
    assert not any("ambiguous" in msg.lower() for msg in messages)


@pytest.mark.asyncio
async def test_case_insensitive_prefix_match(player, moves):
    """Test that prefix matching is case-insensitive."""
    variant_moves = {
        "high": moves["parry high"],
        "low": moves["parry low"],
    }
    handler = combat_commands.make_variant_handler(
        "parry", variant_moves, combat_commands.do_defend
    )

    await handler(player, "HI")

    messages = [call[0][0] for call in player.writer.write.call_args_list]
    assert not any("unknown" in msg.lower() for msg in messages)
    assert not any("ambiguous" in msg.lower() for msg in messages)
