from datetime import timedelta
from django.db import transaction
from django.utils import timezone
from rest_framework.exceptions import APIException, ValidationError
from .security_models import AuthChallenge


def create(purpose, payload, user=None):
    return AuthChallenge.objects.create(purpose=purpose,payload=payload,user=user,
        auth_version=user.auth_version if user else None,expires_at=timezone.now()+timedelta(minutes=5))


def consume(challenge_id, purpose, operation, user=None):
    """Failed attempts commit, while failed credential mutations roll back."""
    error=None
    with transaction.atomic():
        # Bound enrollment/link challenges follow the same user -> challenge
        # lock order as deletion and credential revocation.
        from .models import User
        owner=AuthChallenge.objects.filter(pk=challenge_id,purpose=purpose).values_list('user_id',flat=True).first()
        if owner:
            User.objects.select_for_update().get(pk=owner)
        row=AuthChallenge.objects.select_for_update(of=('self',)).select_related('user').filter(pk=challenge_id,purpose=purpose).first()
        if (row is None or row.consumed_at or row.expires_at<=timezone.now() or row.attempts>=5
            or (user and row.user_id!=user.pk)
            or (row.user_id and (not row.user.is_active or row.user.auth_version!=row.auth_version))):
            raise ValidationError('Invalid, expired or already used challenge.')
        row.attempts+=1;row.save(update_fields=['attempts'])
        try:
            with transaction.atomic():
                result=operation(row)
                row.consumed_at=timezone.now();row.save(update_fields=['consumed_at'])
        except APIException as exc:
            error=exc
    if error:
        raise error
    return result
