"""Server-side game logic. The browser only ever sees the current question, never the answer."""
import random
import secrets
import time
from datetime import timedelta
from decimal import Decimal

from django.utils import timezone

from .models import GameCard, Play

BN = str.maketrans('0123456789', '০১২৩৪৫৬৭৮৯')
LIVES = 3
CLUE_POINTS = [25, 20, 15, 10, 5]
FACT_SECONDS = 15


def bn(value):
    return str(value).translate(BN)


def year_label(card):
    if card.year_label:
        return card.year_label
    if card.year is None:
        return ''
    return f'{bn(-card.year)} খ্রিস্টপূর্ব' if card.year < 0 else f'{bn(card.year)} খ্রি.'


def _cards(game):
    qs = GameCard.objects.filter(game=game, is_active=True)
    if game.kind == 'timeline':
        qs = qs.exclude(year=None)
    elif game.kind == 'whoami':
        qs = qs.exclude(clues='').exclude(wrong_options='')
    else:
        qs = qs.exclude(is_true=None)
    return list(qs.values_list('id', flat=True))


def start(game, user=None, session_key=''):
    ids = _cards(game)
    random.shuffle(ids)
    need = game.rounds + (1 if game.kind == 'timeline' else 0)
    ids = ids[:need]
    if len(ids) < (2 if game.kind == 'timeline' else 1):
        return None
    rounds = len(ids) - (1 if game.kind == 'timeline' else 0)
    per = {'timeline': 10, 'whoami': CLUE_POINTS[0], 'factmyth': 15}[game.kind]
    play = Play.objects.create(game=game, user=user if user is not None and user.is_authenticated else None,
                               session_key=session_key or '', cards=ids, max_score=per * rounds,
                               state={'rounds': rounds, 'placed': ids[:1] if game.kind == 'timeline' else []})
    _prepare(play)
    play.save()
    return play


def _current(play):
    offset = 1 if play.game.kind == 'timeline' else 0
    idx = play.step + offset
    return GameCard.objects.get(pk=play.cards[idx]) if idx < len(play.cards) else None


def _prepare(play):
    """Set up per-question state (options order, clue count, timer)."""
    card = _current(play)
    st = play.state
    st['asked_at'] = time.time()
    if card and play.game.kind == 'whoami':
        options = card.wrong_list()[:3] + [card.title]
        random.shuffle(options)
        st['options'] = options
        st['clues_shown'] = 1


def question(play):
    if play.finished:
        return None
    card = _current(play)
    if card is None:
        return None
    st, kind = play.state, play.game.kind
    q = {'type': kind, 'round': play.step + 1, 'rounds': st['rounds'], 'score': play.score,
         'image': card.image.url if card.image else ''}
    if kind == 'timeline':
        placed = {c.pk: c for c in GameCard.objects.filter(pk__in=st['placed'])}
        q.update(event=card.title, lives=LIVES - play.mistakes,
                 timeline=[{'title': placed[i].title, 'year': year_label(placed[i])} for i in st['placed']])
    elif kind == 'whoami':
        clues = card.clue_list()
        shown = st.get('clues_shown', 1)
        q.update(clues=clues[:shown], more=shown < len(clues), options=st['options'],
                 worth=CLUE_POINTS[min(shown, len(CLUE_POINTS)) - 1])
    else:
        q.update(statement=card.title, seconds=FACT_SECONDS)
    return q


def answer(play, data):
    """Apply one move. Returns the response dict for the browser."""
    if play.finished:
        return {'error': 'This game is over.', 'finished': True}
    card = _current(play)
    st, kind = play.state, play.game.kind
    result = {}

    if kind == 'whoami' and data.get('action') == 'clue':
        clues = card.clue_list()
        st['clues_shown'] = min(st.get('clues_shown', 1) + 1, len(clues))
        play.save(update_fields=['state'])
        return {'question': question(play)}

    if kind == 'timeline':
        try:
            pos = int(data.get('position'))
        except (TypeError, ValueError):
            return {'error': 'Choose a place on the timeline.'}
        placed = list(GameCard.objects.filter(pk__in=st['placed']).values_list('pk', 'year'))
        years = dict(placed)
        order = st['placed']
        pos = max(0, min(pos, len(order)))
        before = years[order[pos - 1]] if pos > 0 else None
        after = years[order[pos]] if pos < len(order) else None
        ok = (before is None or before <= card.year) and (after is None or card.year <= after)
        right = next((i for i, cid in enumerate(order) if years[cid] > card.year), len(order))
        order.insert(pos if ok else right, card.pk)
        st['placed'] = order
        points = 10 if ok else 0
        result.update(position=pos if ok else right, year=year_label(card))
    elif kind == 'whoami':
        try:
            choice = st['options'][int(data.get('option'))]
        except (TypeError, ValueError, IndexError, KeyError):
            return {'error': 'Choose an answer.'}
        ok = choice == card.title
        shown = st.get('clues_shown', 1)
        points = CLUE_POINTS[min(shown, len(CLUE_POINTS)) - 1] if ok else 0
        result.update(answer=card.title, clues=card.clue_list())
    else:
        value = data.get('value')
        if value not in (True, False, 'true', 'false', 'timeout'):
            return {'error': 'Choose true or false.'}
        elapsed = time.time() - st.get('asked_at', 0)
        timed_out = value == 'timeout' or elapsed > FACT_SECONDS + 4  # a little slack for slow networks
        value = value in (True, 'true')
        ok = (value == card.is_true) and not timed_out
        points = (10 + (5 if elapsed <= 5 else 3 if elapsed <= 10 else 0)) if ok else 0
        result.update(truth=card.is_true, timed_out=timed_out)

    play.score += points
    if ok:
        play.correct += 1
    else:
        play.mistakes += 1
    play.step += 1
    result.update(correct=ok, points=points, score=play.score, explanation=card.explanation,
                  read_more=card.article.get_absolute_url() if card.article_id else '')
    out_of_lives = kind == 'timeline' and play.mistakes >= LIVES
    if out_of_lives or play.step >= st['rounds']:
        play.finished = True
        play.finished_at = timezone.now()
    else:
        _prepare(play)
    play.save()
    if play.finished:
        result['finished'] = finish_summary(play)
    else:
        result['question'] = question(play)
    return result


def finish_summary(play):
    game = play.game
    pct = play.percent
    reward = game.reward_percent(pct)
    out = {'score': play.score, 'max': play.max_score, 'percent': pct, 'correct': play.correct,
           'reward_percent': reward, 'min_score': game.reward_min_score, 'coupon': None}
    if play.user_id and reward:
        out.update(issue_reward(play))
    elif reward:
        out['needs_login'] = True
    return out


def issue_reward(play):
    """Create a personal single-use coupon for this play (respects the cooldown). Returns dict for the browser."""
    from apps.store.models import Coupon
    game, user = play.game, play.user
    if play.coupon_id:
        return {'coupon': _coupon_info(play.coupon)}
    pct = game.reward_percent(play.percent)
    if not pct or user is None:
        return {}
    since = timezone.now() - timedelta(days=game.reward_cooldown_days)
    last = (Play.objects.filter(user=user, game=game, coupon__isnull=False, finished_at__gte=since)
            .exclude(pk=play.pk).order_by('-finished_at').first())
    if last:
        nxt = last.finished_at + timedelta(days=game.reward_cooldown_days)
        return {'cooldown_until': timezone.localtime(nxt).strftime('%d %b %Y'),
                'existing': _coupon_info(last.coupon) if last.coupon and last.coupon.is_valid() else None}
    code = f'KHELA-{secrets.token_hex(3).upper()}'
    while Coupon.objects.filter(code=code).exists():
        code = f'KHELA-{secrets.token_hex(3).upper()}'
    coupon = Coupon.objects.create(
        code=code, note=f'{game.title}: score {play.percent}% (play #{play.pk})',
        discount_type='percentage', discount_value=Decimal(pct), max_discount=game.reward_max_taka,
        min_order_amount=game.reward_min_order, applies_to=game.reward_applies_to,
        valid_to=timezone.now() + timedelta(days=game.reward_valid_days),
        max_uses=1, per_customer_limit=1, requires_login=True, owner=user, source='game')
    if game.reward_applies_to == 'selected':
        coupon.products.set(game.reward_products.all())
    play.coupon = coupon
    play.save(update_fields=['coupon'])
    return {'coupon': _coupon_info(coupon)}


def _coupon_info(c):
    return {'code': c.code, 'description': c.describe_bn(),
            'valid_to': timezone.localtime(c.valid_to).strftime('%d %b %Y') if c.valid_to else ''}
