from django.db.models import Q

from apps.epic.models import Room, RoomInvite


# ── Game-wide constants ────────────────────────────────────────────────────
# Reversal probability applied to any card pulled from a stack, anywhere in
# the game (DRAW SEA initially; future phases — gameplay draws etc. — will
# share this single source of truth). Stub for a future per-user profile
# override: callers MUST go through stack_reversal_probability(user, room)
# rather than referencing the constant directly so the user-config hookup is
# a one-line change inside the helper.
STACK_REVERSAL_PROBABILITY = 0.25


def stack_reversal_probability(user=None, room=None):
    """Reversal probability for a draw stack in this user's context.

    Current behavior: returns the module default for everyone. Plumbing point
    for a forthcoming per-user setting — when that lands, swap the body to
    something like `return getattr(user.profile, 'reversal_rate', STACK_REVERSAL_PROBABILITY)`
    and every call site picks up the per-user value automatically.
    """
    return STACK_REVERSAL_PROBABILITY


def card_dict(card, reversal_prob=STACK_REVERSAL_PROBABILITY):
    """Canonical serialization of a TarotCard → JSON payload.

    Single source of truth for the gameroom `sea_deck` endpoint AND the
    `_my_sea_deck_data` helper on /gameboard/my-sea/. Iter 4b of the My
    Sea roadmap extracted this from two near-identical copies that had
    drifted on the Major Arcana polarity-split keys (cards 19-21 + 48-49)
    — keeping the contract in one place prevents the same drift recurring.

    The `reversed` flag is rolled fresh each call via `random.random()`
    against `reversal_prob`. Callers that need a deterministic flag (e.g.
    re-rendering a previously-saved hand) should NOT use this helper —
    look up the card and serialize manually with the persisted flag.
    """
    import random as _random
    return {
        'id': card.id,
        'name': card.name,
        'arcana': card.arcana,
        'suit': card.suit,
        'number': card.number,
        'corner_rank': card.corner_rank,
        'suit_icon': card.suit_icon,
        'name_group': card.name_group,
        'name_title': card.name_title,
        'levity_qualifier': card.levity_qualifier,
        'gravity_qualifier': card.gravity_qualifier,
        'reversal_qualifier': card.reversal_qualifier,
        # Pattern B / B' marker — cards 16-18 set this True so the reversal
        # face renders the name swap WITHOUT a polarity qualifier appended.
        # See `TarotCard.applet_face` docstring for the full pattern table.
        'reversal_drops_qualifier': card.reversal_drops_qualifier,
        # Polarity-split full-title overrides (cards 48-49 + trumps 19-21)
        'levity_emanation': card.levity_emanation,
        'gravity_emanation': card.gravity_emanation,
        'levity_reversal': card.levity_reversal,
        'gravity_reversal': card.gravity_reversal,
        'italic_word': card.italic_word,
        'keywords_upright': card.keywords_upright,
        'keywords_reversed': card.keywords_reversed,
        'energies': card.energies,
        'operations': card.operations,
        'reversed': _random.random() < reversal_prob,
        # Sprint A.7-polish — image-mode payload for the my-sea picker's
        # JS-driven `_fillSlot` (which writes slot.innerHTML on draw). Empty
        # strings for legacy text-only decks (Earthman, RWS); non-empty when
        # `deck.has_card_images=True` (Minchiate today). JS branches on
        # `image_url` to render an <img> instead of corner-rank + suit-icon
        # so the mid-draw slot fill matches the server-rendered saved-hand
        # `_my_sea_slot.html` partial branch.
        'image_url': card.image_url,
        'arcana_key': card.arcana,
    }



# Element key → in-game capacitor name (mirrors ELEMENT_INFO in sky-wheel.js).
# Used by the SKY_SAVED provenance event to render prose like
#   "yields them a unique Ardor capacity."
ELEMENT_CAPACITOR_NAMES = {
    "Fire":  "Ardor",
    "Stone": "Ossum",
    "Time":  "Tempo",
    "Space": "Nexus",
    "Air":   "Pneuma",
    "Water": "Humor",
}
# Canonical clockwise-ring ordering for tie-break and prose joining.
ELEMENT_ORDER = ["Fire", "Stone", "Time", "Space", "Air", "Water"]


def top_capacitors(elements):
    """Return capacitor names tied for the highest count in `elements`.

    `elements` is the chart-data dict whose values are either ints (raw counts)
    or {"count": int, ...} enriched dicts. Order follows ELEMENT_ORDER so tied
    output is deterministic across runs and matches the wheel's visual order.
    """
    if not elements:
        return []
    def _count(v):
        return v.get("count", 0) if isinstance(v, dict) else (v or 0)
    counts = {k: _count(v) for k, v in elements.items()}
    if not counts or max(counts.values()) <= 0:
        return []
    top = max(counts.values())
    return [
        ELEMENT_CAPACITOR_NAMES[k]
        for k in ELEMENT_ORDER
        if counts.get(k) == top and k in ELEMENT_CAPACITOR_NAMES
    ]


def _planet_house(degree, cusps):
    """Return 1-based house number for a planet at ecliptic degree.

    cusps is the 12-element list from PySwiss where cusps[i] is the start of
    house i+1. Handles the wrap-around case where a cusp crosses 0°/360°.
    """
    degree = degree % 360
    for i in range(12):
        start = cusps[i] % 360
        end   = cusps[(i + 1) % 12] % 360
        if start < end:
            if start <= degree < end:
                return i + 1
        else:  # wrap-around: e.g. cusp at 350° → next at 10°
            if degree >= start or degree < end:
                return i + 1
    return 1


def _compute_distinctions(planets, houses):
    """Return dict {house_number_str: planet_count} for all 12 houses."""
    cusps = houses['cusps']
    counts = {str(i): 0 for i in range(1, 13)}
    for planet_data in planets.values():
        h = _planet_house(planet_data['degree'], cusps)
        counts[str(h)] += 1
    return counts


def rooms_for_user(user):
    """Return a queryset of rooms the user owns, has a gate slot in, or is invited to."""
    return Room.objects.filter(
        Q(owner=user) |
        Q(gate_slots__gamer=user) |
        Q(invites__invitee_email=user.email, invites__status=RoomInvite.PENDING)
    ).distinct()


def annotate_latest_event(rooms):
    """Attach `latest_event` (GameEvent or None) to each Room in the iterable.
    Materialises the queryset to a list. Shared by the My Scrolls (billboard)
    + My Games (gameboard) applet rows so they render the same 3-col
    `<title> | <latest event prose> | <ts>` shape from one helper."""
    rooms = list(rooms)
    for r in rooms:
        r.latest_event = r.events.order_by("-timestamp").first()
    return rooms
