"""
Item Generator Service - Procedural item generation with affixes.

This service generates Diablo-style items by combining base templates with
random affixes, creating items like "Flaming Dagger of Strength".
"""

import uuid
import random
from typing import List, Optional, Tuple, Dict, Any

from app.models.items import Item
from app.models.affixes import Affix, BaseItemTemplate
from app.models.enums import ItemType, ItemRarity, DamageType, AffixTier
from app.services.affix_loader import get_affix_loader, AffixLoader
from app.services.base_item_loader import get_base_item_loader, BaseItemLoader
from app.utils.logging import get_logger

logger = get_logger(__file__)

# Affix count by rarity (COMMON/UNCOMMON get 0 affixes - plain items)
AFFIX_COUNTS = {
    ItemRarity.COMMON: 0,
    ItemRarity.UNCOMMON: 0,
    ItemRarity.RARE: 1,
    ItemRarity.EPIC: 2,
    ItemRarity.LEGENDARY: 3,
}

# Tier selection probabilities by rarity
# Higher rarity items have better chance at higher tier affixes
TIER_WEIGHTS = {
    ItemRarity.RARE: {
        AffixTier.MINOR: 0.8,
        AffixTier.MAJOR: 0.2,
        AffixTier.LEGENDARY: 0.0,
    },
    ItemRarity.EPIC: {
        AffixTier.MINOR: 0.3,
        AffixTier.MAJOR: 0.7,
        AffixTier.LEGENDARY: 0.0,
    },
    ItemRarity.LEGENDARY: {
        AffixTier.MINOR: 0.1,
        AffixTier.MAJOR: 0.4,
        AffixTier.LEGENDARY: 0.5,
    },
}

# Rarity value multipliers (higher rarity = more valuable)
RARITY_VALUE_MULTIPLIER = {
    ItemRarity.COMMON: 1.0,
    ItemRarity.UNCOMMON: 1.5,
    ItemRarity.RARE: 2.5,
    ItemRarity.EPIC: 5.0,
    ItemRarity.LEGENDARY: 10.0,
}


class ItemGenerator:
    """
    Generates procedural items with Diablo-style naming.

    This service combines base item templates with randomly selected affixes
    to create unique items with combined stats and generated names.
    """

    def __init__(
        self,
        affix_loader: Optional[AffixLoader] = None,
        base_item_loader: Optional[BaseItemLoader] = None
    ):
        """
        Initialize the item generator.

        Args:
            affix_loader: Optional custom AffixLoader instance
            base_item_loader: Optional custom BaseItemLoader instance
        """
        self.affix_loader = affix_loader or get_affix_loader()
        self.base_item_loader = base_item_loader or get_base_item_loader()

        logger.info("ItemGenerator initialized")

    def generate_item(
        self,
        item_type: str,
        rarity: ItemRarity,
        character_level: int = 1,
        base_template_id: Optional[str] = None
    ) -> Optional[Item]:
        """
        Generate a procedural item.

        Args:
            item_type: "weapon" or "armor"
            rarity: Target rarity
            character_level: Player level for template eligibility
            base_template_id: Optional specific base template to use

        Returns:
            Generated Item instance or None if generation fails
        """
        # 1. Get base template
        base_template = self._get_base_template(
            item_type, rarity, character_level, base_template_id
        )
        if not base_template:
            logger.warning(
                "No base template available",
                item_type=item_type,
                rarity=rarity.value,
                level=character_level
            )
            return None

        # 2. Get affix count for this rarity
        affix_count = AFFIX_COUNTS.get(rarity, 0)

        # 3. Select affixes
        prefixes, suffixes = self._select_affixes(
            base_template.item_type, rarity, affix_count
        )

        # 4. Build the item
        item = self._build_item(base_template, rarity, prefixes, suffixes)

        logger.info(
            "Item generated",
            item_id=item.item_id,
            name=item.get_display_name(),
            rarity=rarity.value,
            affixes=[a.affix_id for a in prefixes + suffixes]
        )

        return item

    def _get_base_template(
        self,
        item_type: str,
        rarity: ItemRarity,
        character_level: int,
        template_id: Optional[str] = None
    ) -> Optional[BaseItemTemplate]:
        """
        Get a base template for item generation.

        Args:
            item_type: Type of item
            rarity: Target rarity
            character_level: Player level
            template_id: Optional specific template ID

        Returns:
            BaseItemTemplate instance or None
        """
        if template_id:
            return self.base_item_loader.get_template(template_id)

        return self.base_item_loader.get_random_template(
            item_type, rarity.value, character_level
        )

    def _select_affixes(
        self,
        item_type: str,
        rarity: ItemRarity,
        count: int
    ) -> Tuple[List[Affix], List[Affix]]:
        """
        Select random affixes for an item.

        Distribution logic:
        - RARE (1 affix): 50% chance prefix, 50% chance suffix
        - EPIC (2 affixes): 1 prefix AND 1 suffix
        - LEGENDARY (3 affixes): Mix of prefixes and suffixes

        Args:
            item_type: Type of item
            rarity: Item rarity
            count: Number of affixes to select

        Returns:
            Tuple of (prefixes, suffixes)
        """
        prefixes: List[Affix] = []
        suffixes: List[Affix] = []
        used_ids: List[str] = []

        if count == 0:
            return prefixes, suffixes

        # Determine tier for affix selection
        tier = self._roll_affix_tier(rarity)

        if count == 1:
            # RARE: Either prefix OR suffix (50/50)
            if random.random() < 0.5:
                prefix = self.affix_loader.get_random_prefix(
                    item_type, rarity.value, tier, used_ids
                )
                if prefix:
                    prefixes.append(prefix)
                    used_ids.append(prefix.affix_id)
            else:
                suffix = self.affix_loader.get_random_suffix(
                    item_type, rarity.value, tier, used_ids
                )
                if suffix:
                    suffixes.append(suffix)
                    used_ids.append(suffix.affix_id)

        elif count == 2:
            # EPIC: 1 prefix AND 1 suffix
            tier = self._roll_affix_tier(rarity)
            prefix = self.affix_loader.get_random_prefix(
                item_type, rarity.value, tier, used_ids
            )
            if prefix:
                prefixes.append(prefix)
                used_ids.append(prefix.affix_id)

            tier = self._roll_affix_tier(rarity)
            suffix = self.affix_loader.get_random_suffix(
                item_type, rarity.value, tier, used_ids
            )
            if suffix:
                suffixes.append(suffix)
                used_ids.append(suffix.affix_id)

        elif count >= 3:
            # LEGENDARY: Mix of prefixes and suffixes
            # Try: 2 prefixes + 1 suffix OR 1 prefix + 2 suffixes
            distribution = random.choice([(2, 1), (1, 2)])
            prefix_count, suffix_count = distribution

            for _ in range(prefix_count):
                tier = self._roll_affix_tier(rarity)
                prefix = self.affix_loader.get_random_prefix(
                    item_type, rarity.value, tier, used_ids
                )
                if prefix:
                    prefixes.append(prefix)
                    used_ids.append(prefix.affix_id)

            for _ in range(suffix_count):
                tier = self._roll_affix_tier(rarity)
                suffix = self.affix_loader.get_random_suffix(
                    item_type, rarity.value, tier, used_ids
                )
                if suffix:
                    suffixes.append(suffix)
                    used_ids.append(suffix.affix_id)

        return prefixes, suffixes

    def _roll_affix_tier(self, rarity: ItemRarity) -> Optional[AffixTier]:
        """
        Roll for affix tier based on item rarity.

        Args:
            rarity: Item rarity

        Returns:
            Selected AffixTier or None for no tier filter
        """
        weights = TIER_WEIGHTS.get(rarity)
        if not weights:
            return None

        tiers = list(weights.keys())
        tier_weights = list(weights.values())

        # Filter out zero-weight options
        valid_tiers = []
        valid_weights = []
        for t, w in zip(tiers, tier_weights):
            if w > 0:
                valid_tiers.append(t)
                valid_weights.append(w)

        if not valid_tiers:
            return None

        return random.choices(valid_tiers, weights=valid_weights, k=1)[0]

    def _build_item(
        self,
        base_template: BaseItemTemplate,
        rarity: ItemRarity,
        prefixes: List[Affix],
        suffixes: List[Affix]
    ) -> Item:
        """
        Build an Item from base template and affixes.

        Args:
            base_template: Base item template
            rarity: Item rarity
            prefixes: List of prefix affixes
            suffixes: List of suffix affixes

        Returns:
            Fully constructed Item instance
        """
        # Generate unique ID
        item_id = f"gen_{uuid.uuid4().hex[:12]}"

        # Build generated name
        generated_name = self._build_name(base_template.name, prefixes, suffixes)

        # Combine stats from all affixes
        combined_stats = self._combine_affix_stats(prefixes + suffixes)

        # Calculate final item values
        item_type = ItemType.WEAPON if base_template.item_type == "weapon" else ItemType.ARMOR

        # Base values from template
        damage = base_template.base_damage + combined_stats["damage_bonus"]
        spell_power = base_template.base_spell_power  # Magical weapon damage
        defense = base_template.base_defense + combined_stats["defense_bonus"]
        resistance = base_template.base_resistance + combined_stats["resistance_bonus"]
        crit_chance = base_template.crit_chance + combined_stats["crit_chance_bonus"]
        crit_multiplier = base_template.crit_multiplier + combined_stats["crit_multiplier_bonus"]

        # Calculate value with rarity multiplier
        base_value = base_template.base_value
        rarity_mult = RARITY_VALUE_MULTIPLIER.get(rarity, 1.0)
        # Add value for each affix
        affix_value = len(prefixes + suffixes) * 25
        final_value = int((base_value + affix_value) * rarity_mult)

        # Determine elemental damage type (from prefix affixes)
        elemental_damage_type = None
        elemental_ratio = 0.0
        for prefix in prefixes:
            if prefix.applies_elemental_damage():
                elemental_damage_type = prefix.damage_type
                elemental_ratio = prefix.elemental_ratio
                break  # Use first elemental prefix

        # Track applied affixes
        applied_affixes = [a.affix_id for a in prefixes + suffixes]

        # Create the item
        item = Item(
            item_id=item_id,
            name=base_template.name,  # Base name
            item_type=item_type,
            rarity=rarity,
            description=base_template.description,
            value=final_value,
            is_tradeable=True,
            stat_bonuses=combined_stats["stat_bonuses"],
            effects_on_use=[],  # Not a consumable
            damage=damage,
            spell_power=spell_power,  # Magical weapon damage bonus
            damage_type=DamageType(base_template.damage_type) if damage > 0 else None,
            crit_chance=crit_chance,
            crit_multiplier=crit_multiplier,
            elemental_damage_type=elemental_damage_type,
            physical_ratio=1.0 - elemental_ratio if elemental_ratio > 0 else 1.0,
            elemental_ratio=elemental_ratio,
            defense=defense,
            resistance=resistance,
            required_level=base_template.required_level,
            required_class=None,
            # Affix tracking
            applied_affixes=applied_affixes,
            base_template_id=base_template.template_id,
            generated_name=generated_name,
            is_generated=True,
        )

        return item

    def _build_name(
        self,
        base_name: str,
        prefixes: List[Affix],
        suffixes: List[Affix]
    ) -> str:
        """
        Build the full item name with affixes.

        Examples:
        - RARE (1 prefix): "Flaming Dagger"
        - RARE (1 suffix): "Dagger of Strength"
        - EPIC: "Flaming Dagger of Strength"
        - LEGENDARY: "Blazing Glacial Dagger of the Titan"

        Note: Rarity is NOT included in name (shown via UI).

        Args:
            base_name: Base item name (e.g., "Dagger")
            prefixes: List of prefix affixes
            suffixes: List of suffix affixes

        Returns:
            Full generated name string
        """
        parts = []

        # Add prefix names (in order)
        for prefix in prefixes:
            parts.append(prefix.name)

        # Add base name
        parts.append(base_name)

        # Build name string from parts
        name = " ".join(parts)

        # Add suffix names (they include "of")
        for suffix in suffixes:
            name += f" {suffix.name}"

        return name

    def _combine_affix_stats(self, affixes: List[Affix]) -> Dict[str, Any]:
        """
        Combine stats from multiple affixes.

        Args:
            affixes: List of affixes to combine

        Returns:
            Dictionary with combined stat values
        """
        combined = {
            "stat_bonuses": {},
            "damage_bonus": 0,
            "defense_bonus": 0,
            "resistance_bonus": 0,
            "crit_chance_bonus": 0.0,
            "crit_multiplier_bonus": 0.0,
        }

        for affix in affixes:
            # Combine stat bonuses
            for stat_name, bonus in affix.stat_bonuses.items():
                current = combined["stat_bonuses"].get(stat_name, 0)
                combined["stat_bonuses"][stat_name] = current + bonus

            # Combine direct bonuses
            combined["damage_bonus"] += affix.damage_bonus
            combined["defense_bonus"] += affix.defense_bonus
            combined["resistance_bonus"] += affix.resistance_bonus
            combined["crit_chance_bonus"] += affix.crit_chance_bonus
            combined["crit_multiplier_bonus"] += affix.crit_multiplier_bonus

        return combined

    def generate_loot_drop(
        self,
        character_level: int,
        luck_stat: int = 8,
        item_type: Optional[str] = None
    ) -> Optional[Item]:
        """
        Generate a random loot drop with luck-influenced rarity.

        Args:
            character_level: Player level
            luck_stat: Player's luck stat (affects rarity chance)
            item_type: Optional item type filter

        Returns:
            Generated Item or None
        """
        # Choose random item type if not specified
        if item_type is None:
            item_type = random.choice(["weapon", "armor"])

        # Roll rarity with luck bonus
        rarity = self._roll_rarity(luck_stat)

        return self.generate_item(item_type, rarity, character_level)

    def _roll_rarity(self, luck_stat: int) -> ItemRarity:
        """
        Roll item rarity with luck bonus.

        Base chances (luck 8):
        - COMMON: 50%
        - UNCOMMON: 30%
        - RARE: 15%
        - EPIC: 4%
        - LEGENDARY: 1%

        Luck modifies these chances slightly.

        Args:
            luck_stat: Player's luck stat

        Returns:
            Rolled ItemRarity
        """
        # Calculate luck bonus (luck 8 = baseline)
        luck_bonus = (luck_stat - 8) * 0.005

        roll = random.random()

        # Thresholds (cumulative)
        legendary_threshold = 0.01 + luck_bonus
        epic_threshold = legendary_threshold + 0.04 + luck_bonus * 2
        rare_threshold = epic_threshold + 0.15 + luck_bonus * 3
        uncommon_threshold = rare_threshold + 0.30

        if roll < legendary_threshold:
            return ItemRarity.LEGENDARY
        elif roll < epic_threshold:
            return ItemRarity.EPIC
        elif roll < rare_threshold:
            return ItemRarity.RARE
        elif roll < uncommon_threshold:
            return ItemRarity.UNCOMMON
        else:
            return ItemRarity.COMMON


# Global instance for convenience
_generator_instance: Optional[ItemGenerator] = None


def get_item_generator() -> ItemGenerator:
    """
    Get the global ItemGenerator instance.

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