import re

from django.contrib.auth.views import redirect_to_login
from django.core.cache import cache
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.decorators.http import require_POST

from .models import Comment, Rating, Reaction
from .registry import ct, get_object
from .stats import summary

LINK_RE = re.compile(r'https?://|www\.', re.I)


def _ajax(request):
    return request.headers.get('x-requested-with') == 'XMLHttpRequest'


def _back(request, obj):
    nxt = request.POST.get('next') or request.META.get('HTTP_REFERER') or ''
    if not url_has_allowed_host_and_scheme(nxt, {request.get_host()}, require_https=request.is_secure()):
        nxt = obj.get_absolute_url()
    return nxt.split('#')[0] + '#engage'


def _guard(request, kind, pk):
    obj = get_object(kind, pk)
    if obj is None:
        raise Http404
    if not request.user.is_authenticated:
        back = obj.get_absolute_url() + '#engage'
        if _ajax(request):
            from django.urls import reverse
            return obj, JsonResponse({'login': f"{reverse('account_login')}?next={back}"}, status=401)
        return obj, redirect_to_login(back)
    return obj, None


def _done(request, obj, extra=None):
    if _ajax(request):
        data = summary(obj, request.user)
        data.update(extra or {})
        return JsonResponse(data)
    return HttpResponseRedirect(_back(request, obj))


def _too_fast(request, action, limit, seconds):
    key = f'engage:{action}:{request.user.pk}'
    n = cache.get(key, 0)
    if n >= limit:
        return True
    cache.set(key, n + 1, seconds)
    return False


@require_POST
def react(request, kind, pk, action):
    obj, stop = _guard(request, kind, pk)
    if stop:
        return stop
    if action not in ('like', 'save'):
        raise Http404
    if _too_fast(request, 'react', 120, 600):
        return JsonResponse({'error': 'Too many clicks — try again in a few minutes.'}, status=429)
    where = dict(user=request.user, content_type=ct(obj), object_id=obj.pk, kind=action)
    deleted, _ = Reaction.objects.filter(**where).delete()
    if not deleted:
        Reaction.objects.create(**where)
    return _done(request, obj)


@require_POST
def rate(request, kind, pk):
    obj, stop = _guard(request, kind, pk)
    if stop:
        return stop
    try:
        stars = int(request.POST.get('stars', 0))
    except ValueError:
        stars = 0
    if not 1 <= stars <= 5:
        return JsonResponse({'error': 'Choose 1 to 5 stars.'}, status=400)
    Rating.objects.update_or_create(user=request.user, content_type=ct(obj), object_id=obj.pk,
                                    defaults={'stars': stars})
    return _done(request, obj)


@require_POST
def comment(request, kind, pk):
    obj, stop = _guard(request, kind, pk)
    if stop:
        return stop
    body = (request.POST.get('body') or '').strip()
    error = ''
    if request.POST.get('website'):  # honeypot
        error = 'Could not post.'
    elif len(body) < 2:
        error = 'Please write a comment first.'
    elif len(body) > 1500:
        error = 'Comments can be up to 1500 characters.'
    elif _too_fast(request, 'comment', 5, 600):
        error = 'You are commenting too fast. Please wait a few minutes.'
    if error:
        if _ajax(request):
            return JsonResponse({'error': error}, status=400)
        from django.contrib import messages
        messages.error(request, error)
        return HttpResponseRedirect(_back(request, obj))
    # Comments with links wait for review (spam). Everything else shows at once; staff can hide later.
    status = 'pending' if len(LINK_RE.findall(body)) > 0 and not request.user.is_staff else 'visible'
    c = Comment.objects.create(user=request.user, content_type=ct(obj), object_id=obj.pk, body=body, status=status)
    extra = {'comment': {'id': c.pk, 'name': c.author_name, 'body': c.body, 'pending': status == 'pending'}}
    if not _ajax(request) and status == 'pending':
        from django.contrib import messages
        messages.info(request, 'ধন্যবাদ! লিংকসহ মন্তব্য প্রকাশের আগে আমরা একবার দেখে নিই।')
    return _done(request, obj, extra)


@require_POST
def delete_comment(request, pk):
    if not request.user.is_authenticated:
        raise Http404
    c = get_object_or_404(Comment, pk=pk, user=request.user)
    obj = c.target
    c.delete()
    if obj is None:
        return HttpResponseRedirect('/')
    return _done(request, obj, {'deleted': pk})
