"""
Seasonal League & Realm Partitioning System for FreeExile.
Provides Path of Exile 2 style League mechanics:
- Permanent Standard Realm (Thái Hư Giới) vs Seasonal Challenge Leagues (Thiên Đạo Luân Hồi).
- Clean slate resets every season (level 1, empty stash, fresh economy).
- Remove-Only stash tabs for non-destructive seasonal merge.
- Hardcore Permadeath immediate migration to Standard.
- Solo Self-Found (SSF) isolation with voluntary opt-in trade migration.
"""

from enum import Enum
import time
import uuid
from typing import Dict, List, Optional, Tuple, Any, Set
from dataclasses import dataclass, field


class LeagueType(str, Enum):
    STANDARD = "standard"                  # Thái Hư Giới - Permanent realm, never resets
    CHALLENGE = "challenge"                # Thiên Đạo Luân Hồi - 3-4 month big update season
    HARDCORE = "hardcore"                  # Tử Sinh Luân Hồi - Permadeath migrates to Standard
    SOLO_SELF_FOUND = "solo_self_found"    # Độc Hành Tu Chân - SSF, no trade, opt-in migration


class LeagueStatus(str, Enum):
    UPCOMING = "upcoming"      # Announced, pre-download, character creation disabled
    ACTIVE = "active"          # Live gameplay, character creation enabled, ladder open
    FREEZING = "freezing"      # Locked trade/creation prior to season migration
    MIGRATING = "migrating"    # Background migration pipeline active
    ARCHIVED = "archived"      # Historical season, ladder snapshot persisted


@dataclass(slots=True)
class SeasonStashItem:
    item_uuid: str
    item_name: str
    stack_count: int = 1
    item_level: int = 1
    rarity: str = "PHAM_PHAM"
    raw_properties: Dict[str, Any] = field(default_factory=dict)


@dataclass(slots=True)
class StashTab:
    tab_id: str
    account_id: str
    league_id: str
    name: str
    is_remove_only: bool = False
    color_hex: str = "#d97706"
    capacity: int = 144
    items: Dict[str, SeasonStashItem] = field(default_factory=dict)

    def can_deposit(self) -> bool:
        return not self.is_remove_only and len(self.items) < self.capacity

    def deposit_item(self, item: SeasonStashItem) -> None:
        if self.is_remove_only:
            raise ValueError("Cannot deposit into Remove-Only stash tab")
        if len(self.items) >= self.capacity:
            raise ValueError(f"Stash tab '{self.name}' is full ({self.capacity} slots)")
        self.items[item.item_uuid] = item

    def withdraw_item(self, item_uuid: str) -> Optional[SeasonStashItem]:
        return self.items.pop(item_uuid, None)

    def is_empty(self) -> bool:
        return len(self.items) == 0

    def is_prunable(self) -> bool:
        """A Remove-Only tab that has been completely emptied can be safely pruned."""
        return self.is_remove_only and self.is_empty()


@dataclass(slots=True)
class SeasonCharacter:
    character_id: str
    account_id: str
    name: str
    martial_class: str
    league_id: str
    level: int = 1
    is_alive: bool = True
    created_at_ms: int = field(default_factory=lambda: int(time.time() * 1000))
    inventory: List[Dict[str, Any]] = field(default_factory=list)
    meridian_nodes_unlocked: Set[str] = field(default_factory=set)


@dataclass(slots=True)
class League:
    league_id: str
    display_name: str
    league_type: LeagueType
    status: LeagueStatus
    version_requirement: str = "v1.0.0"
    start_time_epoch_ms: int = field(default_factory=lambda: int(time.time() * 1000))
    end_time_epoch_ms: int = 0
    mechanic_flags: Dict[str, str] = field(default_factory=dict)
    active_characters_count: int = 0


class SeasonLeagueManager:
    """
    Central Coordinator for Realms and Seasonal Leagues.
    Enforces realm partition rules, character creation gates, and status transitions.
    """

    def __init__(self) -> None:
        self.leagues: Dict[str, League] = {}
        self.characters: Dict[str, SeasonCharacter] = {}
        # (account_id, league_id) -> List[StashTab]
        self.stashes: Dict[Tuple[str, str], List[StashTab]] = {}

        # Initialize the permanent Standard realm by default
        self._init_standard_realm()

    def _init_standard_realm(self) -> None:
        standard = League(
            league_id="standard",
            display_name="Thái Hư Giới (Standard Realm)",
            league_type=LeagueType.STANDARD,
            status=LeagueStatus.ACTIVE,
            version_requirement="v1.0.0",
            mechanic_flags={"core_only": "true"}
        )
        self.leagues["standard"] = standard

    def create_league(
        self,
        league_id: str,
        display_name: str,
        league_type: LeagueType,
        status: LeagueStatus = LeagueStatus.ACTIVE,
        version_requirement: str = "v1.0.0",
        mechanic_flags: Optional[Dict[str, str]] = None
    ) -> League:
        if league_id in self.leagues:
            raise ValueError(f"League with ID '{league_id}' already exists")

        league = League(
            league_id=league_id,
            display_name=display_name,
            league_type=league_type,
            status=status,
            version_requirement=version_requirement,
            mechanic_flags=mechanic_flags or {}
        )
        self.leagues[league_id] = league
        return league

    def get_league(self, league_id: str) -> Optional[League]:
        return self.leagues.get(league_id)

    def set_league_status(self, league_id: str, new_status: LeagueStatus) -> None:
        league = self.leagues.get(league_id)
        if not league:
            raise KeyError(f"League '{league_id}' not found")
        if league.league_type == LeagueType.STANDARD and new_status == LeagueStatus.ARCHIVED:
            raise ValueError("Standard realm cannot be archived")
        league.status = new_status

    def create_character(
        self,
        account_id: str,
        character_name: str,
        martial_class: str,
        league_id: str
    ) -> SeasonCharacter:
        league = self.leagues.get(league_id)
        if not league:
            raise KeyError(f"Target league '{league_id}' does not exist")
        if league.status != LeagueStatus.ACTIVE:
            raise ValueError(f"League '{league_id}' status is '{league.status.value}', which is not active for character creation")

        char_id = f"char_{uuid.uuid4().hex[:12]}"
        char = SeasonCharacter(
            character_id=char_id,
            account_id=account_id,
            name=character_name,
            martial_class=martial_class,
            league_id=league_id,
            level=1,
            is_alive=True
        )
        self.characters[char_id] = char
        league.active_characters_count += 1

        # Ensure default stash exists for this account in this league
        stash_key = (account_id, league_id)
        if stash_key not in self.stashes:
            default_tab = StashTab(
                tab_id=f"stash_{uuid.uuid4().hex[:8]}",
                account_id=account_id,
                league_id=league_id,
                name="Kho Chung 1",
                is_remove_only=False
            )
            self.stashes[stash_key] = [default_tab]

        return char

    def get_character(self, character_id: str) -> Optional[SeasonCharacter]:
        return self.characters.get(character_id)

    def validate_realm_access(self, character_id: str, world_instance_league_id: str) -> bool:
        """
        Validates that a character is strictly authorized to enter an instance of that league.
        Prevents Standard realm characters from entering Seasonal instances and vice versa.
        """
        char = self.characters.get(character_id)
        if not char:
            return False
        return char.league_id == world_instance_league_id

    def handle_hardcore_death(self, character_id: str) -> Tuple[bool, str]:
        """
        Permadeath handler for Hardcore leagues.
        The dead character is not deleted, but immediately migrated to the Standard realm.
        """
        char = self.characters.get(character_id)
        if not char:
            return False, f"Character '{character_id}' not found"

        source_league = self.leagues.get(char.league_id)
        if not source_league or source_league.league_type != LeagueType.HARDCORE:
            return False, f"Character '{character_id}' is not in a Hardcore league"

        # Migrate character to Standard
        old_league_id = char.league_id
        char.league_id = "standard"
        char.is_alive = True  # Resurrected upon transition to Standard

        # Update character counters
        if source_league.active_characters_count > 0:
            source_league.active_characters_count -= 1
        std_league = self.leagues["standard"]
        std_league.active_characters_count += 1

        return True, f"Character '{char.name}' perished in '{old_league_id}' and was migrated to Standard"

    def handle_ssf_voluntary_migration(
        self,
        character_id: str,
        target_trade_league_id: str
    ) -> Tuple[bool, str]:
        """
        Allows a Solo Self-Found character to voluntarily migrate to the trade league of the same season.
        """
        char = self.characters.get(character_id)
        if not char:
            return False, f"Character '{character_id}' not found"

        source_league = self.leagues.get(char.league_id)
        if not source_league or source_league.league_type != LeagueType.SOLO_SELF_FOUND:
            return False, "Only Solo Self-Found characters can request voluntary trade migration"

        target_league = self.leagues.get(target_trade_league_id)
        if not target_league or target_league.status != LeagueStatus.ACTIVE:
            return False, f"Target league '{target_trade_league_id}' is not an active trade league"

        char.league_id = target_trade_league_id
        return True, f"Character '{char.name}' successfully migrated to trade league '{target_trade_league_id}'"

    def get_account_stashes(self, account_id: str, league_id: str) -> List[StashTab]:
        return self.stashes.get((account_id, league_id), [])
