"""
Countdown scheduler for the polarity-room SAVE SIG gate.

Uses threading.Timer so no separate Celery worker is needed in development.
Single-process only — swap for a Celery task if production uses multiple
web workers (gunicorn -w N with N > 1).
"""
import threading
import uuid

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.core.cache import cache

_LEVITY_ROLES = {'PC', 'NC', 'SC'}
_GRAVITY_ROLES = {'BC', 'EC', 'AC'}

# In-process registry of pending timers: "{room_id}_{polarity}" → Timer
_timers = {}


def _cache_key(room_id, polarity):
    return f'sig_countdown_{room_id}_{polarity}'


def _group_send(room_id, msg):
    async_to_sync(get_channel_layer().group_send)(f'room_{room_id}', msg)


def _fire(room_id, polarity, token):
    """Callback run by threading.Timer after the countdown expires."""
    # Token guard: if cancelled or superseded, cache entry will differ
    if cache.get(_cache_key(room_id, polarity)) != token:
        return

    from apps.epic.models import Room, SigReservation

    try:
        room = Room.objects.get(id=room_id)
    except Room.DoesNotExist:
        return

    if room.table_status != Room.SIG_SELECT:
        return

    polarity_roles = _LEVITY_ROLES if polarity == SigReservation.LEVITY else _GRAVITY_ROLES

    # Idempotency: seats already assigned
    if not room.table_seats.filter(role__in=polarity_roles, significator__isnull=True).exists():
        return

    # Safety: all three must still be ready
    ready_reservations = list(
        SigReservation.objects.filter(room=room, polarity=polarity, ready=True)
        .select_related('seat', 'card')
    )
    if len(ready_reservations) < 3:
        return

    for res in ready_reservations:
        if res.seat:
            res.seat.significator = res.card
            res.seat.save(update_fields=['significator'])

    SigReservation.objects.filter(room=room, polarity=polarity).update(countdown_remaining=None)

    _group_send(room_id, {'type': 'polarity_room_done', 'polarity': polarity})

    if not room.table_seats.filter(significator__isnull=True).exists():
        Room.objects.filter(id=room_id).update(table_status=Room.SKY_SELECT)
        _group_send(room_id, {'type': 'pick_sky_available'})

    cache.delete(_cache_key(room_id, polarity))
    _timers.pop(f'{room_id}_{polarity}', None)


def schedule_polarity_confirm(room_id, polarity, seconds):
    """Schedule a polarity confirm `seconds` seconds from now. Cancels any prior timer."""
    cancel_polarity_confirm(room_id, polarity)

    token = str(uuid.uuid4())
    cache.set(_cache_key(room_id, polarity), token, timeout=int(seconds) + 60)

    timer = threading.Timer(seconds, _fire, args=[str(room_id), polarity, token])
    timer.daemon = True
    timer.start()
    _timers[f'{room_id}_{polarity}'] = timer


def cancel_polarity_confirm(room_id, polarity):
    """Cancel any pending confirm for this room + polarity."""
    timer = _timers.pop(f'{room_id}_{polarity}', None)
    if timer:
        timer.cancel()
    cache.delete(_cache_key(room_id, polarity))
