"""
Encounter Generator Service - Generate random combat encounters.

This service generates location-appropriate, level-scaled encounter groups
for the "Search for Monsters" feature. Players can select from generated
encounter options to initiate combat.
"""

import random
import uuid
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
from collections import Counter

from app.models.enemy import EnemyTemplate, EnemyDifficulty
from app.services.enemy_loader import get_enemy_loader
from app.utils.logging import get_logger

logger = get_logger(__file__)


@dataclass
class EncounterGroup:
    """
    A generated encounter option for the player to choose.

    Attributes:
        group_id: Unique identifier for this encounter option
        enemies: List of enemy_ids that will spawn
        enemy_names: Display names for the UI
        display_name: Formatted display string (e.g., "3 Goblin Scouts")
        challenge: Difficulty label ("Easy", "Medium", "Hard", "Boss")
        total_xp: Total XP reward (not displayed to player, used internally)
    """
    group_id: str
    enemies: List[str]  # List of enemy_ids
    enemy_names: List[str]  # Display names
    display_name: str  # Formatted for display
    challenge: str  # "Easy", "Medium", "Hard", "Boss"
    total_xp: int  # Internal tracking, not displayed

    def to_dict(self) -> Dict[str, Any]:
        """Serialize encounter group for API response."""
        return {
            "group_id": self.group_id,
            "enemies": self.enemies,
            "enemy_names": self.enemy_names,
            "display_name": self.display_name,
            "challenge": self.challenge,
        }


class EncounterGenerator:
    """
    Generates random encounter groups for a given location and character level.

    Encounter difficulty is determined by:
    - Character level (higher level = more enemies, harder varieties)
    - Location type (different monsters in different areas)
    - Random variance (some encounters harder/easier than average)
    """

    def __init__(self):
        """Initialize the encounter generator."""
        self.enemy_loader = get_enemy_loader()

    def generate_encounters(
        self,
        location_type: str,
        character_level: int,
        num_encounters: int = 4
    ) -> List[EncounterGroup]:
        """
        Generate multiple encounter options for the player to choose from.

        Args:
            location_type: Type of location (e.g., "forest", "town", "dungeon")
            character_level: Current character level (1-20+)
            num_encounters: Number of encounter options to generate (default 4)

        Returns:
            List of EncounterGroup options, each with different difficulty
        """
        # Get enemies available at this location
        available_enemies = self.enemy_loader.get_enemies_by_location(location_type)

        if not available_enemies:
            logger.warning(
                "No enemies found for location",
                location_type=location_type
            )
            return []

        # Generate a mix of difficulties
        # Always try to include: 1 Easy, 1-2 Medium, 0-1 Hard
        encounters = []
        difficulty_mix = self._get_difficulty_mix(character_level, num_encounters)

        for target_difficulty in difficulty_mix:
            encounter = self._generate_single_encounter(
                available_enemies=available_enemies,
                character_level=character_level,
                target_difficulty=target_difficulty
            )
            if encounter:
                encounters.append(encounter)

        logger.info(
            "Generated encounters",
            location_type=location_type,
            character_level=character_level,
            num_encounters=len(encounters)
        )

        return encounters

    def _get_difficulty_mix(
        self,
        character_level: int,
        num_encounters: int
    ) -> List[str]:
        """
        Determine the mix of encounter difficulties to generate.

        Lower-level characters see more easy encounters.
        Higher-level characters see more hard encounters.

        Args:
            character_level: Character's current level
            num_encounters: Total encounters to generate

        Returns:
            List of target difficulty strings
        """
        if character_level <= 2:
            # Very low level: mostly easy
            mix = ["Easy", "Easy", "Medium", "Easy"]
        elif character_level <= 5:
            # Low level: easy and medium
            mix = ["Easy", "Medium", "Medium", "Hard"]
        elif character_level <= 10:
            # Mid level: balanced
            mix = ["Easy", "Medium", "Hard", "Hard"]
        else:
            # High level: harder encounters
            mix = ["Medium", "Hard", "Hard", "Boss"]

        return mix[:num_encounters]

    def _generate_single_encounter(
        self,
        available_enemies: List[EnemyTemplate],
        character_level: int,
        target_difficulty: str
    ) -> Optional[EncounterGroup]:
        """
        Generate a single encounter group.

        Args:
            available_enemies: Pool of enemies to choose from
            character_level: Character's level for scaling
            target_difficulty: Target difficulty ("Easy", "Medium", "Hard", "Boss")

        Returns:
            EncounterGroup or None if generation fails
        """
        # Map target difficulty to enemy difficulty levels
        difficulty_mapping = {
            "Easy": [EnemyDifficulty.EASY],
            "Medium": [EnemyDifficulty.EASY, EnemyDifficulty.MEDIUM],
            "Hard": [EnemyDifficulty.MEDIUM, EnemyDifficulty.HARD],
            "Boss": [EnemyDifficulty.HARD, EnemyDifficulty.BOSS],
        }

        allowed_difficulties = difficulty_mapping.get(target_difficulty, [EnemyDifficulty.EASY])

        # Filter enemies by difficulty
        candidates = [
            e for e in available_enemies
            if e.difficulty in allowed_difficulties
        ]

        if not candidates:
            # Fall back to any available enemy
            candidates = available_enemies

        if not candidates:
            return None

        # Determine enemy count based on difficulty and level
        enemy_count = self._calculate_enemy_count(
            target_difficulty=target_difficulty,
            character_level=character_level
        )

        # Select enemies (allowing duplicates for packs)
        selected_enemies = random.choices(candidates, k=enemy_count)

        # Build encounter group
        enemy_ids = [e.enemy_id for e in selected_enemies]
        enemy_names = [e.name for e in selected_enemies]
        total_xp = sum(e.experience_reward for e in selected_enemies)

        # Create display name (e.g., "3 Goblin Scouts" or "2 Goblins, 1 Goblin Shaman")
        display_name = self._format_display_name(enemy_names)

        return EncounterGroup(
            group_id=f"enc_{uuid.uuid4().hex[:8]}",
            enemies=enemy_ids,
            enemy_names=enemy_names,
            display_name=display_name,
            challenge=target_difficulty,
            total_xp=total_xp
        )

    def _calculate_enemy_count(
        self,
        target_difficulty: str,
        character_level: int
    ) -> int:
        """
        Calculate how many enemies should be in the encounter.

        Args:
            target_difficulty: Target difficulty level
            character_level: Character's level

        Returns:
            Number of enemies to include
        """
        # Base counts by difficulty
        base_counts = {
            "Easy": (1, 2),      # 1-2 enemies
            "Medium": (2, 3),   # 2-3 enemies
            "Hard": (2, 4),     # 2-4 enemies
            "Boss": (1, 3),     # 1 boss + 0-2 adds
        }

        min_count, max_count = base_counts.get(target_difficulty, (1, 2))

        # Scale slightly with level (higher level = can handle more)
        level_bonus = min(character_level // 5, 2)  # +1 enemy every 5 levels, max +2
        max_count = min(max_count + level_bonus, 6)  # Cap at 6 enemies

        return random.randint(min_count, max_count)

    def _format_display_name(self, enemy_names: List[str]) -> str:
        """
        Format enemy names for display.

        Examples:
            ["Goblin Scout"] -> "Goblin Scout"
            ["Goblin Scout", "Goblin Scout", "Goblin Scout"] -> "3 Goblin Scouts"
            ["Goblin Scout", "Goblin Shaman"] -> "Goblin Scout, Goblin Shaman"

        Args:
            enemy_names: List of enemy display names

        Returns:
            Formatted display string
        """
        if len(enemy_names) == 1:
            return enemy_names[0]

        # Count occurrences
        counts = Counter(enemy_names)

        if len(counts) == 1:
            # All same enemy type
            name = list(counts.keys())[0]
            count = list(counts.values())[0]
            # Simple pluralization
            if count > 1:
                if name.endswith('f'):
                    # wolf -> wolves
                    plural_name = name[:-1] + "ves"
                elif name.endswith('s') or name.endswith('x') or name.endswith('ch'):
                    plural_name = name + "es"
                else:
                    plural_name = name + "s"
                return f"{count} {plural_name}"
            return name
        else:
            # Mixed enemy types - list them
            parts = []
            for name, count in counts.items():
                if count > 1:
                    parts.append(f"{count}x {name}")
                else:
                    parts.append(name)
            return ", ".join(parts)


# Global instance
_generator_instance: Optional[EncounterGenerator] = None


def get_encounter_generator() -> EncounterGenerator:
    """
    Get the global EncounterGenerator instance.

    Returns:
        Singleton EncounterGenerator instance
    """
    global _generator_instance
    if _generator_instance is None:
        _generator_instance = EncounterGenerator()
    return _generator_instance
