"""
Core Tarot deck and card classes.

This module defines the Deck class for managing Tarot cards and the Card,
MajorCard, and MinorCard classes for representing individual cards.
"""

import random
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple

from ..attributes import (
    CardImage,
    Color,
    Element,
    ElementType,
    Meaning,
    Path,
    PeriodicTable,
    Planet,
    Sephera,
    Suit,
)
from ..constants import (
    COURT_RANKS,
    MAJOR_ARCANA_NAMES,
    MINOR_RANK_NAMES,
    PIP_INDEX_TO_NUMBER,
    PIP_ORDER,
    SUITS_FIRST,
    SUITS_LAST,
)

if TYPE_CHECKING:
    from ..card.data import CardDataLoader

# Global CardDataLoader instance for accessing elements
_card_data = None  # Will be initialized lazily


def _get_card_data():
    """Get or initialize the global CardDataLoader instance."""
    global _card_data
    if _card_data is None:
        from ..card.data import CardDataLoader

        _card_data = CardDataLoader()
    return _card_data


@dataclass
class Card:
    """Base class representing a Tarot card."""

    number: int
    name: str
    meaning: Meaning
    arcana: str  # "Major" or "Minor"
    image: Optional[CardImage] = None

    # These are overridden in subclasses but declared here for MinorCard compatibility
    suit: Optional[Suit] = None
    pip: int = 0

    # Card-specific details
    explanation: Dict[str, str] = field(default_factory=dict)
    interpretation: str = ""
    keywords: List[str] = field(default_factory=list)
    reversed_keywords: List[str] = field(default_factory=list)
    guidance: str = ""
    numerology: Optional[int] = None

    # Image path for custom deck images
    image_path: Optional[str] = None

    def __str__(self) -> str:
        return f"{self.number}. {self.name}"

    def __repr__(self) -> str:
        return f"Card({self.number}, '{self.name}')"

    def key(self) -> str:
        """
        Get the card's key as a Roman numeral representation.

        Returns:
            Roman numeral string (e.g., "I", "XXI") for Major Arcana,
            or the pip number as string for Minor Arcana.
        """
        # Import here to avoid circular imports
        from ..card.details import CardDetailsRegistry

        # For Major Arcana cards, convert the key to Roman numerals
        if self.arcana == "Major":
            return CardDetailsRegistry.key_to_roman(self.number)

        # For Minor Arcana, return the pip number as a formatted string
        if hasattr(self, "pip") and self.pip > 0:
            pip_names = {
                2: "Two",
                3: "Three",
                4: "Four",
                5: "Five",
                6: "Six",
                7: "Seven",
                8: "Eight",
                9: "Nine",
                10: "Ten",
            }
            return pip_names.get(self.pip, str(self.pip))

        return str(self.number)

    @property
    def type(self) -> str:
        """Get the specific card type (Major, Pip, Ace, Court)."""
        if isinstance(self, MajorCard):
            return "Major"
        elif isinstance(self, AceCard):
            return "Ace"
        elif isinstance(self, CourtCard):
            return "Court"
        elif isinstance(self, PipCard):
            return "Pip"
        return "Unknown"


@dataclass
class MajorCard(Card):
    """Represents a Major Arcana card."""

    kabbalistic_number: Optional[int] = None
    tarot_letter: Optional[str] = None
    tree_of_life_path: Optional[int] = None

    def __post_init__(self) -> None:
        # Kabbalistic number should be 0-21, but deck position can be anywhere
        if self.kabbalistic_number is not None and (
            self.kabbalistic_number < 0 or self.kabbalistic_number > 21
        ):
            raise ValueError(
                f"Major Arcana kabbalistic number must be 0-21, got {self.kabbalistic_number}"
            )
        self.arcana = "Major"


@dataclass
class MinorCard(Card):
    """Represents a Minor Arcana card - either Pip or Court card."""

    suit: Suit = None  # type: ignore
    astrological_influence: Optional[str] = None
    element: Optional[Element] = None

    def __post_init__(self) -> None:
        if self.suit is None:
            raise ValueError("suit must be provided for MinorCard")
        self.arcana = "Minor"


@dataclass
class PipCard(MinorCard):
    """Represents a Pip card (2 through 10) - has a pip number.

    Pip cards represent numbered forces in their suit, from Two
    through its full development (10).
    """

    pip: int = 0

    def __post_init__(self) -> None:
        if not (2 <= self.pip <= 10):
            raise ValueError(f"Pip card number must be 2-10, got {self.pip}")
        super().__post_init__()


@dataclass
class AceCard(MinorCard):
    """Represents an Ace card - the root/foundation of the suit.

    The Ace is the initial force of the suit and contains the potential
    for all other cards within that suit. Aces have pip=1 but are not
    technically pip cards.
    """

    pip: int = 1

    def __post_init__(self) -> None:
        if self.pip != 1:
            raise ValueError(f"AceCard must have pip 1, got {self.pip}")
        super().__post_init__()


@dataclass
class CourtCard(MinorCard):
    """Represents a Court Card - Knight, Prince, Princess, or Queen.

    Court cards represent people/personalities and are the highest rank
    in the minor arcana. They do NOT have pips - they are archetypes.

    Each court card is associated with an element and Hebrew letter (Path):
    - Knight: Fire + Yod (path 20)
    - Prince: Air + Vav (path 16)
    - Princess: Earth + Heh (path 15)
    - Queen: Water + Heh (path 15)
    """

    COURT_RANKS = {"Knight": 12, "Prince": 11, "Princess": 13, "Queen": 14}
    court_rank: str = ""
    associated_element: Optional[ElementType] = None
    hebrew_letter_path: Optional["Path"] = None

    def __post_init__(self) -> None:
        if self.court_rank not in self.COURT_RANKS:
            raise ValueError(
                f"CourtCard must have court_rank in {list(self.COURT_RANKS.keys())}, "
                f"got {self.court_rank}"
            )
        super().__post_init__()


class CardQuery:
    """Helper class for fluent card queries: deck.number(3).minor.wands"""

    def __init__(
        self, deck: "Deck", number: Optional[int] = None, arcana: Optional[str] = None
    ) -> None:
        self.deck = deck
        self.number = number
        self.arcana = arcana

    def _filter_cards(self) -> List[Card]:
        """Get filtered cards based on current query state."""
        cards = self.deck.cards

        if self.number is not None:
            cards = [
                c
                for c in cards
                if c.number == self.number or (hasattr(c, "pip") and c.pip == self.number)
            ]

        if self.arcana is not None:
            cards = [c for c in cards if c.arcana == self.arcana]

        return cards

    @property
    def major(self) -> List[Card]:
        """Filter to Major Arcana only."""
        return [c for c in self._filter_cards() if c.arcana == "Major"]

    @property
    def minor(self) -> "CardQuery":
        """Filter to Minor Arcana, return new CardQuery for suit chaining."""
        return CardQuery(self.deck, self.number, "Minor")

    @property
    def cups(self) -> List[Card]:
        """Get cards in Cups suit."""
        return [
            c
            for c in self._filter_cards()
            if hasattr(c, "suit") and c.suit and c.suit.name == "Cups"
        ]

    @property
    def swords(self) -> List[Card]:
        """Get cards in Swords suit."""
        return [
            c
            for c in self._filter_cards()
            if hasattr(c, "suit") and c.suit and c.suit.name == "Swords"
        ]

    @property
    def wands(self) -> List[Card]:
        """Get cards in Wands suit."""
        return [
            c
            for c in self._filter_cards()
            if hasattr(c, "suit") and c.suit and c.suit.name == "Wands"
        ]

    @property
    def pentacles(self) -> List[Card]:
        """Get cards in Pentacles suit."""
        return [
            c
            for c in self._filter_cards()
            if hasattr(c, "suit") and c.suit and c.suit.name == "Pentacles"
        ]

    def __iter__(self):
        """Allow iteration over filtered cards."""
        return iter(self._filter_cards())

    def __len__(self) -> int:
        """Return count of filtered cards."""
        return len(self._filter_cards())

    def __getitem__(self, index: int) -> Card:
        """Get card by index from filtered results."""
        return self._filter_cards()[index]

    def __repr__(self) -> str:
        cards = self._filter_cards()
        names = [c.name for c in cards]
        return f"CardQuery({names})"


class TemporalQuery:
    """Helper class for fluent temporal queries: loader.month(5).day(23).hour(15)"""

    def __init__(
        self,
        loader: "CardDataLoader",
        month_num: Optional[int] = None,
        day_num: Optional[int] = None,
        hour_num: Optional[int] = None,
    ) -> None:
        """
        Initialize temporal query builder.

        Args:
            loader: CardDataLoader instance for fetching temporal data
            month_num: Month number (1-12)
            day_num: Day number (1-31)
            hour_num: Hour number (0-23)
        """
        self.loader = loader
        self.month_num = month_num
        self.day_num = day_num
        self.hour_num = hour_num

    def month(self, num: int) -> "TemporalQuery":
        """Set month (1-12) and return new query for chaining."""
        return TemporalQuery(
            self.loader, month_num=num, day_num=self.day_num, hour_num=self.hour_num
        )

    def day(self, num: int) -> "TemporalQuery":
        """Set day (1-31) and return new query for chaining."""
        if self.month_num is None:
            raise ValueError("Must set month before day")
        return TemporalQuery(
            self.loader, month_num=self.month_num, day_num=num, hour_num=self.hour_num
        )

    def hour(self, num: int) -> "TemporalQuery":
        """Set hour (0-23) and return new query for chaining."""
        if self.month_num is None or self.day_num is None:
            raise ValueError("Must set month and day before hour")
        return TemporalQuery(
            self.loader, month_num=self.month_num, day_num=self.day_num, hour_num=num
        )

    def weekday(self) -> Optional[str]:
        """Get weekday name for current month/day combination using Zeller's congruence."""
        if self.month_num is None or self.day_num is None:
            raise ValueError("Must set month and day to get weekday")

        # Zeller's congruence (adjusted for current calendar)
        month = self.month_num
        day = self.day_num
        year = 2024  # Use current year as reference

        # Adjust month and year for March-based calculation
        if month < 3:
            month += 12
            year -= 1

        # Zeller's formula
        q = day
        m = month
        k = year % 100
        j = year // 100

        h = (q + ((13 * (m + 1)) // 5) + k + (k // 4) + (j // 4) - (2 * j)) % 7

        # Convert to weekday name (0=Saturday, 1=Sunday, 2=Monday, ..., 6=Friday)
        day_names = ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
        return day_names[h]

    def month_info(self):
        """Return month metadata for the configured query."""
        if self.month_num is None:
            return None
        return self.loader.month_info(self.month_num)

    def day_info(self):
        """Return day metadata for the configured query."""
        if self.day_num is None:
            return None
        return self.loader.day_info(self.day_num)

    def hour_info(self):
        """Return the planetary hour metadata for the configured query."""
        if self.hour_num is None:
            return None
        return self.loader.clock_hour(self.hour_num)

    def __repr__(self) -> str:
        parts = []
        if self.month_num:
            parts.append(f"month={self.month_num}")
        if self.day_num:
            parts.append(f"day={self.day_num}")
        if self.hour_num:
            parts.append(f"hour={self.hour_num}")
        return f"TemporalQuery({', '.join(parts)})"


class DLT:
    """
    Double Letter Trump (DLT) accessor.

    Double Letter Trumps are Major Arcana cards 3-21 (19 cards total),
    each associated with a Hebrew letter and planetary/astrological force.

    Usage:
        dlt = DLT(3)  # Get the 3rd Double Letter Trump (The Empress)
        dlt = DLT(7)  # Get the 7th Double Letter Trump (The Chariot)
    """

    def __init__(self, trump_number: int) -> None:
        """
        Initialize a Double Letter Trump query.

        Args:
            trump_number: Position in DLT sequence (3-21)

        Raises:
            ValueError: If trump_number is not 3-21
        """
        if not 3 <= trump_number <= 21:
            raise ValueError(f"DLT number must be 3-21, got {trump_number}")

        self.trump_number = trump_number
        self._loader: Optional["CardDataLoader"] = None
        self._deck: Optional[Deck] = None

    @property
    def loader(self) -> "CardDataLoader":
        """Lazy-load CardDataLoader on first access."""
        if self._loader is None:
            from ..card.data import CardDataLoader

            self._loader = CardDataLoader()
        return self._loader

    @property
    def deck(self) -> "Deck":
        """Lazy-load Deck on first access."""
        if self._deck is None:
            self._deck = Deck()
        return self._deck

    def card(self) -> Optional[Card]:
        """Get the Tarot card for this DLT."""
        # Major Arcana cards are numbered 0-21, so DLT(3) = Major card 3
        for card in self.deck.cards:
            if card.arcana == "Major" and card.number == self.trump_number:
                return card
        return None

    def periodic_entry(self) -> Optional[PeriodicTable]:
        """Get the periodic table entry with cross-correspondences."""
        return self.loader.periodic_entry(self.trump_number)

    def sephera(self) -> Optional[Sephera]:
        """Get the Sephira associated with this DLT."""
        return self.loader.sephera(self.trump_number)

    def planet(self) -> Optional[Planet]:
        """Get the planetary ruler for this DLT."""
        periodic = self.periodic_entry()
        return periodic.planet if periodic else None

    def element(self) -> Optional[ElementType]:
        """Get the element associated with this DLT."""
        periodic = self.periodic_entry()
        return periodic.element if periodic else None

    def hebrew_letter(self) -> Optional[str]:
        """Get the Hebrew letter associated with this DLT."""
        periodic = self.periodic_entry()
        return periodic.hebrew_letter if periodic else None

    def color(self) -> Optional[Color]:
        """Get the color associated with this DLT."""
        periodic = self.periodic_entry()
        return periodic.color if periodic else None

    def __repr__(self) -> str:
        card = self.card()
        card_name = card.name if card else "Unknown"
        return f"DLT({self.trump_number}) - {card_name}"

    def __str__(self) -> str:
        return self.__repr__()


class Deck:
    """Represents a standard 78-card Tarot deck."""

    def __init__(self) -> None:
        """Initialize a standard Tarot deck with all 78 cards."""
        self.cards: List[Card] = []
        self.discard_pile: List[Card] = []
        self._initialize_deck()

    def _initialize_deck(self) -> None:
        """Initialize the deck with all 78 Tarot cards.

         Order: Cups (1-14), Pentacles/Disks (15-28), Swords (29-42),
             Major Arcana (43-64), Wands (65-78)

         Minor suit sequencing (per suit): Ace, 2-10, Prince, Knight, Princess, Queen.

        This puts Queen of Wands as card #78, the final card.
        """
        # Minor Arcana - First three suits (Cups, Pentacles, Swords)
        # Organized logically: Ace, 10, 2-9, then court cards Knight, Prince, Princess, Queen
        # Get ElementType instances from CardDataLoader
        card_data = _get_card_data()
        water_element = card_data.element("Water")
        earth_element = card_data.element("Earth")
        air_element = card_data.element("Air")
        fire_element = card_data.element("Fire")

        if not water_element or not earth_element or not air_element or not fire_element:
            raise RuntimeError("Failed to load element data from CardDataLoader")

        # Get Hebrew letters (Paths) for court cards
        yod_path = card_data.path(20)  # Yod
        vav_path = card_data.path(16)  # Vav
        he_path = card_data.path(15)  # He (Heh)

        if not yod_path or not vav_path or not he_path:
            raise RuntimeError("Failed to load Hebrew letter/path data from CardDataLoader")

        # Map court ranks to their associated elements and Hebrew letter paths
        # Knight -> Fire + Yod, Prince -> Air + Vav, Princess -> Earth + Heh, Queen -> Water + Heh
        court_rank_mappings = {
            "Knight": (fire_element, yod_path),
            "Prince": (air_element, vav_path),
            "Princess": (earth_element, he_path),
            "Queen": (water_element, he_path),
        }

        element_lookup = {
            "Water": water_element,
            "Earth": earth_element,
            "Air": air_element,
            "Fire": fire_element,
        }

        def _suit_specs(
            suit_defs: List[Tuple[str, str, int]],
        ) -> List[Tuple[str, ElementType, int]]:
            specs: List[Tuple[str, ElementType, int]] = []
            for suit_name, element_key, suit_num in suit_defs:
                element_obj = element_lookup.get(element_key)
                if element_obj is None:
                    raise RuntimeError(
                        f"Failed to resolve element '{element_key}' for suit '{suit_name}'"
                    )
                specs.append((suit_name, element_obj, suit_num))
            return specs

        suits_data_first = _suit_specs(SUITS_FIRST)
        suits_data_last = _suit_specs(SUITS_LAST)

        card_number = 1

        # Loop through first three suits
        for suit_name, element_obj, suit_num in suits_data_first:
            card_number = self._add_minor_cards_for_suit(
                suit_name,
                element_obj,
                suit_num,
                card_number,
                court_rank_mappings,
            )

        # Major Arcana (43-64)
        # Names match filenames in src/tarot/deck/default/
        for i, name in enumerate(MAJOR_ARCANA_NAMES):
            card = MajorCard(
                number=card_number,
                name=name,
                meaning=Meaning(
                    upright=f"{name} upright meaning", reversed=f"{name} reversed meaning"
                ),
                arcana="Major",
                kabbalistic_number=i,
            )
            self.cards.append(card)
            card_number += 1

        # Minor Arcana - Last suit (Wands, 65-78)
        # Organized logically: Ace, 2-10, then court cards Prince, Knight, Princess, Queen
        for suit_name, element_obj, suit_num in suits_data_last:
            card_number = self._add_minor_cards_for_suit(
                suit_name,
                element_obj,
                suit_num,
                card_number,
                court_rank_mappings,
            )

        # Load detailed explanations and keywords from registry
        try:
            from ..card.loader import load_deck_details

            load_deck_details(self)
        except ImportError:
            # Handle case where loader might not be available or circular import issues
            pass

    def _add_minor_cards_for_suit(
        self,
        suit_name: str,
        element_obj: ElementType,
        suit_num: int,
        card_number: int,
        court_rank_mappings: Dict[str, Tuple[ElementType, Path]],
    ) -> int:
        """Add all minor cards for a suit using shared ordering constants."""
        suit = Suit(
            name=suit_name,
            element=element_obj,
            tarot_correspondence=f"{suit_name} Suit",
            number=suit_num,
        )

        for pip_index in PIP_ORDER:
            if pip_index <= 10:
                actual_pip = PIP_INDEX_TO_NUMBER[pip_index]
                name = f"{MINOR_RANK_NAMES[pip_index]} of {suit_name}"
                card_kwargs = {
                    "number": card_number,
                    "name": name,
                    "meaning": Meaning(
                        upright=f"{name} upright",
                        reversed=f"{name} reversed",
                    ),
                    "arcana": "Minor",
                    "suit": suit,
                    "pip": actual_pip,
                }
                if pip_index == 1:
                    card = AceCard(**card_kwargs)
                else:
                    card = PipCard(**card_kwargs)
            else:
                court_rank = COURT_RANKS[pip_index]
                associated_element, hebrew_letter_path = court_rank_mappings[court_rank]
                name = f"{MINOR_RANK_NAMES[pip_index]} of {suit_name}"
                card = CourtCard(
                    number=card_number,
                    name=name,
                    meaning=Meaning(
                        upright=f"{name} upright",
                        reversed=f"{name} reversed",
                    ),
                    arcana="Minor",
                    suit=suit,
                    court_rank=court_rank,
                    associated_element=associated_element,
                    hebrew_letter_path=hebrew_letter_path,
                )

            self.cards.append(card)
            card_number += 1

        return card_number

    def shuffle(self) -> None:
        """Shuffle the deck."""
        random.shuffle(self.cards)

    def draw(self, num_cards: int = 1) -> List[Card]:
        """
        Draw cards from the deck.

        Args:
            num_cards: Number of cards to draw (default: 1)

        Returns:
            List of drawn cards
        """
        if num_cards < 1:
            raise ValueError("Must draw at least 1 card")

        if num_cards > len(self.cards):
            raise ValueError(
                f"Cannot draw {num_cards} cards from deck with {len(self.cards)} cards"
            )

        drawn = []
        for _ in range(num_cards):
            drawn.append(self.cards.pop(0))

        return drawn

    def reset(self) -> None:
        """Reset the deck to its initial state."""
        self.cards.clear()
        self.discard_pile.clear()
        self._initialize_deck()

    def remaining(self) -> int:
        """Return the number of cards remaining in the deck."""
        return len(self.cards)

    def number(self, pip_value: int) -> CardQuery:
        """
        Query cards by number (pip value).

        Usage:
            deck.number(3)              # All cards with 3
            deck.number(3).minor        # All minor 3s
            deck.number(3).minor.wands  # 3 of Wands
        """
        return CardQuery(self, pip_value)

    def suit(self, suit_name: str) -> List[Card]:
        """
        Get all cards from a specific suit.

        Usage:
            deck.suit("Wands")
        """
        return [c for c in self.cards if hasattr(c, "suit") and c.suit and c.suit.name == suit_name]

    @property
    def major(self) -> List[Card]:
        """Get all Major Arcana cards."""
        return [c for c in self.cards if c.arcana == "Major"]

    @property
    def minor(self) -> List[Card]:
        """Get all Minor Arcana cards."""
        return [c for c in self.cards if c.arcana == "Minor"]

    def __len__(self) -> int:
        """Return the number of cards in the deck."""
        return len(self.cards)

    def __repr__(self) -> str:
        return f"Deck({len(self.cards)} cards remaining)"
