"""
Unit tests for EnemyTemplate model and EnemyLoader service.

Tests enemy loading, serialization, and filtering functionality.
"""

import pytest
from pathlib import Path

from app.models.enemy import EnemyTemplate, EnemyDifficulty, LootEntry
from app.models.stats import Stats
from app.services.enemy_loader import EnemyLoader


# =============================================================================
# EnemyTemplate Model Tests
# =============================================================================

class TestEnemyTemplate:
    """Tests for EnemyTemplate dataclass."""

    def test_create_basic_enemy(self):
        """Test creating an enemy with minimal attributes."""
        enemy = EnemyTemplate(
            enemy_id="test_enemy",
            name="Test Enemy",
            description="A test enemy",
            base_stats=Stats(strength=10, constitution=8),
        )

        assert enemy.enemy_id == "test_enemy"
        assert enemy.name == "Test Enemy"
        assert enemy.base_stats.strength == 10
        assert enemy.difficulty == EnemyDifficulty.EASY  # Default

    def test_enemy_with_full_attributes(self):
        """Test creating an enemy with all attributes."""
        loot = [
            LootEntry(item_id="sword", drop_chance=0.5),
            LootEntry(item_id="gold", drop_chance=1.0, quantity_min=5, quantity_max=10),
        ]

        enemy = EnemyTemplate(
            enemy_id="goblin_boss",
            name="Goblin Boss",
            description="A fearsome goblin leader",
            base_stats=Stats(strength=14, dexterity=12, constitution=12),
            abilities=["basic_attack", "power_strike"],
            loot_table=loot,
            experience_reward=100,
            gold_reward_min=20,
            gold_reward_max=50,
            difficulty=EnemyDifficulty.HARD,
            tags=["humanoid", "goblinoid", "boss"],
            base_damage=12,
            crit_chance=0.15,
            flee_chance=0.25,
        )

        assert enemy.enemy_id == "goblin_boss"
        assert enemy.experience_reward == 100
        assert enemy.difficulty == EnemyDifficulty.HARD
        assert len(enemy.loot_table) == 2
        assert len(enemy.abilities) == 2
        assert "boss" in enemy.tags

    def test_is_boss(self):
        """Test boss detection."""
        easy_enemy = EnemyTemplate(
            enemy_id="minion",
            name="Minion",
            description="",
            base_stats=Stats(),
            difficulty=EnemyDifficulty.EASY,
        )
        boss_enemy = EnemyTemplate(
            enemy_id="boss",
            name="Boss",
            description="",
            base_stats=Stats(),
            difficulty=EnemyDifficulty.BOSS,
        )

        assert not easy_enemy.is_boss()
        assert boss_enemy.is_boss()

    def test_has_tag(self):
        """Test tag checking."""
        enemy = EnemyTemplate(
            enemy_id="zombie",
            name="Zombie",
            description="",
            base_stats=Stats(),
            tags=["undead", "slow", "Humanoid"],  # Mixed case
        )

        assert enemy.has_tag("undead")
        assert enemy.has_tag("UNDEAD")  # Case insensitive
        assert enemy.has_tag("humanoid")
        assert not enemy.has_tag("beast")

    def test_get_gold_reward(self):
        """Test gold reward generation."""
        enemy = EnemyTemplate(
            enemy_id="test",
            name="Test",
            description="",
            base_stats=Stats(),
            gold_reward_min=10,
            gold_reward_max=20,
        )

        # Run multiple times to check range
        for _ in range(50):
            gold = enemy.get_gold_reward()
            assert 10 <= gold <= 20

    def test_roll_loot_empty_table(self):
        """Test loot rolling with empty table."""
        enemy = EnemyTemplate(
            enemy_id="test",
            name="Test",
            description="",
            base_stats=Stats(),
            loot_table=[],
        )

        drops = enemy.roll_loot()
        assert drops == []

    def test_roll_loot_guaranteed_drop(self):
        """Test loot rolling with guaranteed drop."""
        enemy = EnemyTemplate(
            enemy_id="test",
            name="Test",
            description="",
            base_stats=Stats(),
            loot_table=[
                LootEntry(item_id="guaranteed_item", drop_chance=1.0),
            ],
        )

        drops = enemy.roll_loot()
        assert len(drops) == 1
        assert drops[0]["item_id"] == "guaranteed_item"

    def test_serialization_round_trip(self):
        """Test that to_dict/from_dict preserves data."""
        original = EnemyTemplate(
            enemy_id="test_enemy",
            name="Test Enemy",
            description="A test description",
            base_stats=Stats(strength=15, dexterity=12, luck=10),
            abilities=["attack", "defend"],
            loot_table=[
                LootEntry(item_id="sword", drop_chance=0.5),
            ],
            experience_reward=50,
            gold_reward_min=10,
            gold_reward_max=25,
            difficulty=EnemyDifficulty.MEDIUM,
            tags=["humanoid", "test"],
            base_damage=8,
            crit_chance=0.10,
            flee_chance=0.40,
        )

        # Serialize and deserialize
        data = original.to_dict()
        restored = EnemyTemplate.from_dict(data)

        # Verify all fields match
        assert restored.enemy_id == original.enemy_id
        assert restored.name == original.name
        assert restored.description == original.description
        assert restored.base_stats.strength == original.base_stats.strength
        assert restored.base_stats.luck == original.base_stats.luck
        assert restored.abilities == original.abilities
        assert len(restored.loot_table) == len(original.loot_table)
        assert restored.experience_reward == original.experience_reward
        assert restored.gold_reward_min == original.gold_reward_min
        assert restored.gold_reward_max == original.gold_reward_max
        assert restored.difficulty == original.difficulty
        assert restored.tags == original.tags
        assert restored.base_damage == original.base_damage
        assert restored.crit_chance == pytest.approx(original.crit_chance)
        assert restored.flee_chance == pytest.approx(original.flee_chance)


class TestLootEntry:
    """Tests for LootEntry dataclass."""

    def test_create_loot_entry(self):
        """Test creating a loot entry."""
        entry = LootEntry(
            item_id="gold_coin",
            drop_chance=0.75,
            quantity_min=5,
            quantity_max=15,
        )

        assert entry.item_id == "gold_coin"
        assert entry.drop_chance == 0.75
        assert entry.quantity_min == 5
        assert entry.quantity_max == 15

    def test_loot_entry_defaults(self):
        """Test loot entry default values."""
        entry = LootEntry(item_id="item")

        assert entry.drop_chance == 0.1
        assert entry.quantity_min == 1
        assert entry.quantity_max == 1


# =============================================================================
# EnemyLoader Service Tests
# =============================================================================

class TestEnemyLoader:
    """Tests for EnemyLoader service."""

    @pytest.fixture
    def loader(self):
        """Create an enemy loader with the actual data directory."""
        return EnemyLoader()

    def test_load_goblin(self, loader):
        """Test loading the goblin enemy."""
        enemy = loader.load_enemy("goblin")

        assert enemy is not None
        assert enemy.enemy_id == "goblin"
        assert enemy.name == "Goblin Scout"
        assert enemy.difficulty == EnemyDifficulty.EASY
        assert "humanoid" in enemy.tags
        assert "goblinoid" in enemy.tags

    def test_load_goblin_shaman(self, loader):
        """Test loading the goblin shaman."""
        enemy = loader.load_enemy("goblin_shaman")

        assert enemy is not None
        assert enemy.enemy_id == "goblin_shaman"
        assert enemy.base_stats.intelligence == 12  # Caster stats
        assert "caster" in enemy.tags

    def test_load_dire_wolf(self, loader):
        """Test loading the dire wolf."""
        enemy = loader.load_enemy("dire_wolf")

        assert enemy is not None
        assert enemy.difficulty == EnemyDifficulty.MEDIUM
        assert "beast" in enemy.tags
        assert enemy.base_stats.strength == 14

    def test_load_bandit(self, loader):
        """Test loading the bandit."""
        enemy = loader.load_enemy("bandit")

        assert enemy is not None
        assert enemy.difficulty == EnemyDifficulty.MEDIUM
        assert "rogue" in enemy.tags
        assert enemy.crit_chance == 0.12

    def test_load_skeleton_warrior(self, loader):
        """Test loading the skeleton warrior."""
        enemy = loader.load_enemy("skeleton_warrior")

        assert enemy is not None
        assert "undead" in enemy.tags
        assert "fearless" in enemy.tags

    def test_load_orc_berserker(self, loader):
        """Test loading the orc berserker."""
        enemy = loader.load_enemy("orc_berserker")

        assert enemy is not None
        assert enemy.difficulty == EnemyDifficulty.HARD
        assert enemy.base_stats.strength == 18
        assert enemy.base_damage == 15

    def test_load_nonexistent_enemy(self, loader):
        """Test loading an enemy that doesn't exist."""
        enemy = loader.load_enemy("nonexistent_enemy_12345")

        assert enemy is None

    def test_load_all_enemies(self, loader):
        """Test loading all enemies."""
        enemies = loader.load_all_enemies()

        # Should have at least our 6 sample enemies
        assert len(enemies) >= 6
        assert "goblin" in enemies
        assert "goblin_shaman" in enemies
        assert "dire_wolf" in enemies
        assert "bandit" in enemies
        assert "skeleton_warrior" in enemies
        assert "orc_berserker" in enemies

    def test_get_enemies_by_difficulty(self, loader):
        """Test filtering enemies by difficulty."""
        loader.load_all_enemies()  # Ensure loaded

        easy_enemies = loader.get_enemies_by_difficulty(EnemyDifficulty.EASY)
        medium_enemies = loader.get_enemies_by_difficulty(EnemyDifficulty.MEDIUM)
        hard_enemies = loader.get_enemies_by_difficulty(EnemyDifficulty.HARD)

        # Check we got enemies in each category
        assert len(easy_enemies) >= 2  # goblin, goblin_shaman
        assert len(medium_enemies) >= 3  # dire_wolf, bandit, skeleton_warrior
        assert len(hard_enemies) >= 1  # orc_berserker

        # Verify difficulty is correct
        for enemy in easy_enemies:
            assert enemy.difficulty == EnemyDifficulty.EASY

    def test_get_enemies_by_tag(self, loader):
        """Test filtering enemies by tag."""
        loader.load_all_enemies()

        humanoids = loader.get_enemies_by_tag("humanoid")
        undead = loader.get_enemies_by_tag("undead")
        beasts = loader.get_enemies_by_tag("beast")

        # Verify results
        assert len(humanoids) >= 3  # goblin, goblin_shaman, bandit, orc
        assert len(undead) >= 1  # skeleton_warrior
        assert len(beasts) >= 1  # dire_wolf

        # Verify tags
        for enemy in humanoids:
            assert enemy.has_tag("humanoid")

    def test_get_random_enemies(self, loader):
        """Test random enemy selection."""
        loader.load_all_enemies()

        # Get 3 random enemies
        random_enemies = loader.get_random_enemies(count=3)

        assert len(random_enemies) == 3
        # All should be EnemyTemplate instances
        for enemy in random_enemies:
            assert isinstance(enemy, EnemyTemplate)

    def test_get_random_enemies_with_filters(self, loader):
        """Test random selection with difficulty filter."""
        loader.load_all_enemies()

        # Get only easy enemies
        easy_enemies = loader.get_random_enemies(
            count=5,
            difficulty=EnemyDifficulty.EASY,
        )

        # All returned enemies should be easy
        for enemy in easy_enemies:
            assert enemy.difficulty == EnemyDifficulty.EASY

    def test_cache_behavior(self, loader):
        """Test that caching works correctly."""
        # Load an enemy twice
        enemy1 = loader.load_enemy("goblin")
        enemy2 = loader.load_enemy("goblin")

        # Should be the same object (cached)
        assert enemy1 is enemy2

        # Clear cache
        loader.clear_cache()

        # Load again
        enemy3 = loader.load_enemy("goblin")

        # Should be a new object
        assert enemy3 is not enemy1
        assert enemy3.enemy_id == enemy1.enemy_id


# =============================================================================
# EnemyDifficulty Enum Tests
# =============================================================================

class TestEnemyDifficulty:
    """Tests for EnemyDifficulty enum."""

    def test_difficulty_values(self):
        """Test difficulty enum values."""
        assert EnemyDifficulty.EASY.value == "easy"
        assert EnemyDifficulty.MEDIUM.value == "medium"
        assert EnemyDifficulty.HARD.value == "hard"
        assert EnemyDifficulty.BOSS.value == "boss"

    def test_difficulty_from_string(self):
        """Test creating difficulty from string."""
        assert EnemyDifficulty("easy") == EnemyDifficulty.EASY
        assert EnemyDifficulty("hard") == EnemyDifficulty.HARD
