"""Tests for target resolution system."""

from mudlib.entity import Mob
from mudlib.targeting import (
    find_entity_on_tile,
    find_in_inventory,
    find_thing_on_tile,
    parse_target,
    resolve_target,
)
from mudlib.thing import Thing


class TestParseTarget:
    """Test ordinal prefix parsing."""

    def test_plain_name(self):
        """Plain name returns ordinal 1."""
        ordinal, name = parse_target("goblin")
        assert ordinal == 1
        assert name == "goblin"

    def test_ordinal_prefix(self):
        """Parse '2.goblin' into (2, 'goblin')."""
        ordinal, name = parse_target("2.goblin")
        assert ordinal == 2
        assert name == "goblin"

    def test_first_ordinal(self):
        """Parse '1.sword' into (1, 'sword')."""
        ordinal, name = parse_target("1.sword")
        assert ordinal == 1
        assert name == "sword"

    def test_large_ordinal(self):
        """Parse large ordinal numbers."""
        ordinal, name = parse_target("10.rat")
        assert ordinal == 10
        assert name == "rat"

    def test_zero_ordinal_invalid(self):
        """Zero ordinal is invalid, treat as plain name."""
        ordinal, name = parse_target("0.sword")
        assert ordinal == 1
        assert name == "0.sword"

    def test_negative_ordinal_invalid(self):
        """Negative ordinal is invalid, treat as plain name."""
        ordinal, name = parse_target("-1.sword")
        assert ordinal == 1
        assert name == "-1.sword"


class TestResolveTarget:
    """Test target resolution with name matching."""

    def test_exact_match(self):
        """Find exact name match."""
        candidates = [Thing(name="sword"), Thing(name="shield")]
        result = resolve_target("sword", candidates)
        assert result is candidates[0]

    def test_exact_match_case_insensitive(self):
        """Exact match is case-insensitive."""
        candidates = [Thing(name="Sword")]
        result = resolve_target("sword", candidates)
        assert result is candidates[0]

    def test_prefix_match(self):
        """Find by name prefix."""
        candidates = [Thing(name="longsword")]
        result = resolve_target("long", candidates)
        assert result is candidates[0]

    def test_prefix_match_case_insensitive(self):
        """Prefix match is case-insensitive."""
        candidates = [Thing(name="LongSword")]
        result = resolve_target("long", candidates)
        assert result is candidates[0]

    def test_alias_exact_match(self):
        """Find by exact alias match."""
        candidates = [Thing(name="longsword", aliases=["blade", "weapon"])]
        result = resolve_target("blade", candidates)
        assert result is candidates[0]

    def test_alias_prefix_match(self):
        """Find by alias prefix."""
        candidates = [Thing(name="longsword", aliases=["greatsword"])]
        result = resolve_target("great", candidates)
        assert result is candidates[0]

    def test_ordinal_disambiguation(self):
        """Use ordinal to select Nth match."""
        candidates = [
            Thing(name="goblin"),
            Thing(name="goblin"),
            Thing(name="goblin"),
        ]
        result = resolve_target("2.goblin", candidates)
        assert result is candidates[1]

    def test_ordinal_out_of_range(self):
        """Ordinal beyond available matches returns None."""
        candidates = [Thing(name="goblin")]
        result = resolve_target("2.goblin", candidates)
        assert result is None

    def test_no_match(self):
        """No matching candidates returns None."""
        candidates = [Thing(name="sword")]
        result = resolve_target("shield", candidates)
        assert result is None

    def test_custom_key_function(self):
        """Use custom key function to extract name."""

        class CustomObject:
            def __init__(self, label):
                self.label = label

        candidates = [CustomObject("sword"), CustomObject("shield")]
        result = resolve_target("sword", candidates, key=lambda obj: obj.label)
        assert result is candidates[0]

    def test_object_without_aliases(self):
        """Handle objects without aliases attribute."""

        class SimpleObject:
            def __init__(self, name):
                self.name = name

        candidates = [SimpleObject("sword")]
        result = resolve_target("sword", candidates)
        assert result is candidates[0]

    def test_exact_match_preferred_over_prefix(self):
        """Exact match takes precedence over prefix."""
        candidates = [Thing(name="sword"), Thing(name="swordfish")]
        result = resolve_target("sword", candidates)
        assert result is candidates[0]

    def test_ordinal_with_prefix_match(self):
        """Ordinal works with prefix matching."""
        candidates = [
            Thing(name="longsword"),
            Thing(name="longbow"),
        ]
        result = resolve_target("2.long", candidates)
        assert result is candidates[1]


class TestFindEntityOnTile:
    """Test finding entities on the same tile."""

    def test_find_nearby_player(self, player, nearby_player, test_zone):
        """Find another player on same tile."""
        player.location = test_zone
        nearby_player.location = test_zone
        result = find_entity_on_tile("vegeta", player)
        assert result is nearby_player

    def test_find_mob_on_tile(self, player, test_zone):
        """Find mob on same tile."""
        player.location = test_zone
        goblin = Mob(name="goblin", x=0, y=0, alive=True)
        goblin.location = test_zone
        test_zone._contents.append(goblin)
        result = find_entity_on_tile("goblin", player)
        assert result is goblin

    def test_skip_dead_mobs(self, player, test_zone):
        """Skip dead mobs."""
        player.location = test_zone
        dead_goblin = Mob(name="goblin", x=0, y=0, alive=False)
        dead_goblin.location = test_zone
        test_zone._contents.append(dead_goblin)
        result = find_entity_on_tile("goblin", player)
        assert result is None

    def test_skip_self(self, player, test_zone):
        """Don't return the player themselves."""
        player.location = test_zone
        result = find_entity_on_tile("goku", player)
        assert result is None

    def test_z_axis_check_both_grounded(self, player, nearby_player, test_zone):
        """Find entity when both grounded."""
        player.location = test_zone
        nearby_player.location = test_zone
        player.flying = False
        nearby_player.flying = False
        result = find_entity_on_tile("vegeta", player)
        assert result is nearby_player

    def test_z_axis_check_both_flying(self, player, nearby_player, test_zone):
        """Find entity when both flying."""
        player.location = test_zone
        nearby_player.location = test_zone
        player.flying = True
        nearby_player.flying = True
        result = find_entity_on_tile("vegeta", player)
        assert result is nearby_player

    def test_z_axis_check_mismatch(self, player, nearby_player, test_zone):
        """Don't find entity on different z-axis."""
        player.location = test_zone
        nearby_player.location = test_zone
        player.flying = False
        nearby_player.flying = True
        result = find_entity_on_tile("vegeta", player)
        assert result is None

    def test_ordinal_with_multiple_entities(self, player, test_zone):
        """Use ordinal to select specific entity."""
        player.location = test_zone
        goblin1 = Mob(name="goblin", x=0, y=0, alive=True)
        goblin2 = Mob(name="goblin", x=0, y=0, alive=True)
        goblin1.location = test_zone
        test_zone._contents.append(goblin1)
        goblin2.location = test_zone
        test_zone._contents.append(goblin2)
        result = find_entity_on_tile("2.goblin", player)
        assert result is goblin2

    def test_prefix_match_entity(self, player, test_zone):
        """Find entity by prefix."""
        player.location = test_zone
        goblin = Mob(name="goblin", x=0, y=0, alive=True)
        goblin.location = test_zone
        test_zone._contents.append(goblin)
        result = find_entity_on_tile("gob", player)
        assert result is goblin

    def test_no_match(self, player, test_zone):
        """Return None when no match."""
        player.location = test_zone
        result = find_entity_on_tile("dragon", player)
        assert result is None


class TestFindThingOnTile:
    """Test finding Things on the ground."""

    def test_find_by_name(self, test_zone):
        """Find thing by exact name."""
        sword = Thing(name="sword", x=5, y=5)
        sword.location = test_zone
        test_zone._contents.append(sword)
        result = find_thing_on_tile("sword", test_zone, 5, 5)
        assert result is sword

    def test_find_by_alias(self, test_zone):
        """Find thing by alias."""
        sword = Thing(name="longsword", aliases=["blade"], x=5, y=5)
        sword.location = test_zone
        test_zone._contents.append(sword)
        result = find_thing_on_tile("blade", test_zone, 5, 5)
        assert result is sword

    def test_prefix_match(self, test_zone):
        """Find thing by name prefix."""
        sword = Thing(name="longsword", x=5, y=5)
        sword.location = test_zone
        test_zone._contents.append(sword)
        result = find_thing_on_tile("long", test_zone, 5, 5)
        assert result is sword

    def test_ordinal(self, test_zone):
        """Use ordinal to select specific thing."""
        rock1 = Thing(name="rock", x=5, y=5)
        rock2 = Thing(name="rock", x=5, y=5)
        rock1.location = test_zone
        test_zone._contents.append(rock1)
        rock2.location = test_zone
        test_zone._contents.append(rock2)
        result = find_thing_on_tile("2.rock", test_zone, 5, 5)
        assert result is rock2

    def test_skip_non_things(self, test_zone):
        """Only match Thing instances, skip entities."""
        mob = Mob(name="sword", x=5, y=5, alive=True)
        mob.location = test_zone
        test_zone._contents.append(mob)
        result = find_thing_on_tile("sword", test_zone, 5, 5)
        assert result is None

    def test_no_match(self, test_zone):
        """Return None when no match."""
        result = find_thing_on_tile("shield", test_zone, 5, 5)
        assert result is None


class TestFindInInventory:
    """Test finding Things in player inventory."""

    def test_find_by_name(self, player):
        """Find item by exact name."""
        sword = Thing(name="sword")
        sword.location = player
        player._contents.append(sword)
        result = find_in_inventory("sword", player)
        assert result is sword

    def test_find_by_alias(self, player):
        """Find item by alias."""
        sword = Thing(name="longsword", aliases=["blade"])
        sword.location = player
        player._contents.append(sword)
        result = find_in_inventory("blade", player)
        assert result is sword

    def test_prefix_match(self, player):
        """Find item by name prefix."""
        sword = Thing(name="longsword")
        sword.location = player
        player._contents.append(sword)
        result = find_in_inventory("long", player)
        assert result is sword

    def test_ordinal(self, player):
        """Use ordinal to select specific item."""
        potion1 = Thing(name="potion")
        potion2 = Thing(name="potion")
        potion1.location = player
        player._contents.append(potion1)
        potion2.location = player
        player._contents.append(potion2)
        result = find_in_inventory("2.potion", player)
        assert result is potion2

    def test_no_match(self, player):
        """Return None when no match."""
        result = find_in_inventory("shield", player)
        assert result is None
