from django.conf import settings
from django.contrib import messages
from django.contrib.auth import authenticate, get_user_model, login, logout, update_session_auth_hash
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
from django.contrib.auth.tokens import default_token_generator
from django.core.cache import cache
from django.core.mail import send_mail
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.encoding import force_bytes, force_str
from django.utils.http import url_has_allowed_host_and_scheme, urlsafe_base64_decode, urlsafe_base64_encode
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_POST

from .backends import normalize_phone
from .forms import TERMS_REQUIRED, ClaimForm, LoginForm, ProfileForm, SignupForm

User = get_user_model()
MAX_FAILURES = 5
LOCK_SECONDS = 15 * 60


def _next_url(request, default='account'):
    nxt = request.POST.get('next') or request.GET.get('next')
    if nxt and url_has_allowed_host_and_scheme(nxt, allowed_hosts={request.get_host()}, require_https=request.is_secure()):
        return nxt
    return reverse(default)


def _client_ip(request):
    fwd = request.META.get('HTTP_X_FORWARDED_FOR')
    return fwd.split(',')[0].strip() if fwd else request.META.get('REMOTE_ADDR', '')


def _site_url():
    return getattr(settings, 'SITE_URL', 'https://thereversetimes.com')


def send_verification(request, user):
    uid = urlsafe_base64_encode(force_bytes(user.pk))
    token = default_token_generator.make_token(user)
    link = request.build_absolute_uri(reverse('account_verify', args=[uid, token]))
    html = render_to_string('accounts/email/verify.html', {'user': user, 'link': link})
    send_mail('[The Reverse Times] Confirm your email', f'Confirm your email: {link}',
              settings.DEFAULT_FROM_EMAIL, [user.email], html_message=html, fail_silently=True)


@never_cache
def signup(request):
    if request.user.is_authenticated:
        return redirect('account')
    form = SignupForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        user = form.save()
        login(request, user, backend='apps.accounts.backends.EmailOrPhoneBackend')
        if user.profile.wants_newsletter:
            from apps.newsletter.models import NewsletterSubscriber
            NewsletterSubscriber.objects.get_or_create(email=user.email, defaults={'name': user.get_full_name()})
        send_verification(request, user)
        messages.success(request, f'Welcome, {user.first_name}! We sent a confirmation link to {user.email}.')
        return redirect(_next_url(request))
    return render(request, 'accounts/signup.html', {'form': form, 'next': request.GET.get('next', '')})


@never_cache
def signin(request):
    if request.user.is_authenticated:
        return redirect(_next_url(request))
    form = LoginForm(request.POST or None)
    locked = False
    if request.method == 'POST' and form.is_valid():
        ident = form.cleaned_data['identifier'].strip().lower()
        key = f'login-fail:{_client_ip(request)}:{normalize_phone(ident) if "@" not in ident else ident}'
        failures = cache.get(key, 0)
        if failures >= MAX_FAILURES:
            locked = True
            form.add_error(None, 'Too many wrong attempts. Please wait 15 minutes or reset your password.')
        else:
            user = authenticate(request, username=ident, password=form.cleaned_data['password'])
            if user is None:
                cache.set(key, failures + 1, LOCK_SECONDS)
                form.add_error(None, 'That email/mobile and password don’t match. Please try again.')
            else:
                cache.delete(key)
                login(request, user)
                user.profile.accept_terms()
                if not form.cleaned_data['remember']:
                    request.session.set_expiry(0)  # ends when the browser closes
                return redirect(_next_url(request))
    return render(request, 'accounts/login.html', {'form': form, 'next': request.GET.get('next', ''), 'locked': locked})


@require_POST
def signout(request):
    logout(request)
    messages.info(request, 'You are signed out.')
    return redirect('home')


@never_cache
@login_required
def dashboard(request):
    from apps.store.models import DigitalAccess
    user = request.user
    claim_form = ClaimForm()
    return render(request, 'accounts/dashboard.html', {
        'profile': user.profile,
        'magazines': DigitalAccess.objects.filter(user=user).select_related('product').order_by('-created_at'),
        'orders': user.shop_orders.prefetch_related('items__product').order_by('-created_at')[:20],
        'claim_form': claim_form,
        'saved': _saved_items(user),
        'game_coupons': [c for c in user.coupons.filter(is_active=True).order_by('-created_at')[:10] if c.is_valid()],
        'best_scores': user.game_plays.filter(finished=True).select_related('game').order_by('-score')[:5],
    })


@never_cache
@login_required
def order_detail(request, pk):
    order = get_object_or_404(request.user.shop_orders.prefetch_related('items__product'), pk=pk)
    return render(request, 'accounts/order_detail.html', {'order': order, 'steps': order.timeline()})


def _saved_items(user):
    """Everything the reader saved: articles, magazine issues and games, newest first."""
    from apps.engage.models import Reaction
    out = []
    for r in Reaction.objects.filter(user=user, kind='save').select_related('content_type')[:30]:
        obj = r.target
        if obj is None:
            continue
        label = {'article': 'Article', 'product': 'Magazine', 'game': 'Game'}.get(r.content_type.model, '')
        out.append({'title': str(obj), 'url': obj.get_absolute_url(), 'label': label, 'when': r.created_at})
    return out


@never_cache
@login_required
def account_settings(request):
    user, profile = request.user, request.user.profile
    profile_form = ProfileForm(user=user, initial={
        'name': user.get_full_name(), 'phone': profile.phone or '', 'alt_phone': profile.alt_phone,
        'division': profile.division, 'district': profile.district, 'area': profile.area,
        'address': profile.address, 'wants_newsletter': profile.wants_newsletter})
    # Google-only readers have no password yet: let them set one without the "old password" box
    PasswordForm = PasswordChangeForm if user.has_usable_password() else SetPasswordForm
    password_form = PasswordForm(user)
    if request.method == 'POST' and request.POST.get('form') == 'profile':
        profile_form = ProfileForm(request.POST, user=user)
        if profile_form.is_valid():
            d = profile_form.cleaned_data
            parts = d['name'].strip().split(' ', 1)
            user.first_name, user.last_name = parts[0][:150], (parts[1] if len(parts) > 1 else '')[:150]
            user.save(update_fields=['first_name', 'last_name'])
            profile.phone, profile.address, profile.wants_newsletter = d['phone'], d['address'], d['wants_newsletter']
            profile.alt_phone = d['alt_phone'] or ''
            profile.division, profile.district, profile.area = d['division'], d['district'], d['area']
            profile.save()
            _sync_newsletter(user)
            messages.success(request, 'Your details were saved.')
            return redirect('account_settings')
    elif request.method == 'POST' and request.POST.get('form') == 'password':
        password_form = PasswordForm(user, request.POST)
        if password_form.is_valid():
            password_form.save()
            update_session_auth_hash(request, password_form.user)
            messages.success(request, 'Your password was changed.')
            return redirect('account_settings')
    return render(request, 'accounts/settings.html', {'profile_form': profile_form, 'password_form': password_form,
                                                      'has_password': user.has_usable_password(),
                                                      'google_linked': bool(profile.google_sub)})


def _sync_newsletter(user):
    from apps.newsletter.models import NewsletterSubscriber
    if user.profile.wants_newsletter:
        sub, _ = NewsletterSubscriber.objects.get_or_create(email=user.email, defaults={'name': user.get_full_name()})
        if user.profile.email_verified and not sub.is_confirmed:
            from django.utils import timezone
            sub.is_confirmed, sub.confirmed_at = True, timezone.now()
            sub.save(update_fields=['is_confirmed', 'confirmed_at'])
    else:
        NewsletterSubscriber.objects.filter(email__iexact=user.email).delete()


@login_required
@require_POST
def resend_verification(request):
    if not request.user.profile.email_verified:
        send_verification(request, request.user)
        messages.success(request, f'We sent a new confirmation link to {request.user.email}.')
    return redirect('account')


def verify_email(request, uidb64, token):
    try:
        user = User.objects.get(pk=force_str(urlsafe_base64_decode(uidb64)))
    except (User.DoesNotExist, ValueError, TypeError, OverflowError):
        user = None
    if user is not None and default_token_generator.check_token(user, token):
        user.profile.email_verified = True
        user.profile.save(update_fields=['email_verified'])
        _sync_newsletter(user)
        messages.success(request, 'Your email is confirmed. Thank you!')
    else:
        messages.error(request, 'This confirmation link is invalid or has expired.')
    return redirect('account' if request.user.is_authenticated else 'account_login')


@login_required
@require_POST
def claim_purchase(request):
    """Attach an earlier magazine purchase or shop order to this account (needs phone + secret code)."""
    from apps.store import magazine as mag
    from apps.store.models import DigitalAccess, Order
    form = ClaimForm(request.POST)
    found = 0
    if form.is_valid():
        phone = normalize_phone(form.cleaned_data['phone'])
        code = mag.normalize_trx(form.cleaned_data['code'])
        for a in DigitalAccess.objects.filter(phone=phone, user__isnull=True):
            if code in (a.trx_id, mag.normalize_trx(a.reference)):
                a.user = request.user
                a.save(update_fields=['user'])
                found += 1
        if code.isdigit():
            for o in Order.objects.filter(pk=int(code), user__isnull=True):
                if normalize_phone(o.phone) == phone:
                    o.user = request.user
                    o.save(update_fields=['user'])
                    found += 1
    if found:
        messages.success(request, 'Found it! The purchase is now in your account.')
    else:
        messages.error(request, 'We couldn’t find a purchase with that mobile number and code.')
    return redirect('account')


# ─── Continue with Google ──────────────────────────────────────────────────────

import json

from django.http import JsonResponse

GOOGLE_ISSUERS = {'accounts.google.com', 'https://accounts.google.com'}


def verify_google_credential(credential):
    """Check Google's signed ID token. Returns its claims, or None if it's not genuine."""
    from google.auth.transport import requests as google_requests
    from google.oauth2 import id_token
    try:
        claims = id_token.verify_oauth2_token(credential, google_requests.Request(), settings.GOOGLE_CLIENT_ID)
    except Exception:
        return None
    if claims.get('iss') not in GOOGLE_ISSUERS or not claims.get('email_verified') or not claims.get('email'):
        return None
    return claims


@require_POST
def google_signin(request):
    if not settings.GOOGLE_CLIENT_ID:
        return JsonResponse({'ok': False, 'error': 'Google sign-in is not set up yet.'}, status=503)
    try:
        data = json.loads(request.body or '{}')
    except ValueError:
        return JsonResponse({'ok': False, 'error': 'Invalid request.'}, status=400)
    claims = verify_google_credential(str(data.get('credential', '')))
    if not claims:
        return JsonResponse({'ok': False, 'error': 'Google sign-in could not be verified. Please try again.'}, status=400)
    if data.get('accept_terms') is not True:
        return JsonResponse({'ok': False, 'error': TERMS_REQUIRED}, status=400)

    from .models import CustomerProfile
    sub, email = claims['sub'], claims['email'].strip().lower()
    profile = CustomerProfile.objects.select_related('user').filter(google_sub=sub).first()
    user = profile.user if profile else None
    new_account = False

    if user is None:
        user = User.objects.filter(email__iexact=email).order_by('id').first()
        if user is not None:
            profile = CustomerProfile.objects.get_or_create(user=user)[0]
            if not profile.email_verified and user.has_usable_password() and not user.is_staff:
                # Someone may have registered this address without owning it. Google has just
                # proved ownership, so the unconfirmed password is removed (owner can reset it).
                user.set_unusable_password()
                user.save(update_fields=['password'])
                messages.info(request, 'For your security, please sign in with Google from now on. '
                                       'You can set a new password in Account settings.')
        else:
            user = User.objects.create_user(
                username=email[:150], email=email,
                first_name=(claims.get('given_name') or claims.get('name') or '')[:150],
                last_name=(claims.get('family_name') or '')[:150])
            user.set_unusable_password()
            user.save(update_fields=['password'])
            profile = CustomerProfile.objects.get_or_create(user=user)[0]
            new_account = True
        profile.google_sub = sub
        profile.email_verified = True
        profile.save(update_fields=['google_sub', 'email_verified'])

    if not user.is_active:
        return JsonResponse({'ok': False, 'error': 'This account is disabled.'}, status=403)
    login(request, user, backend='apps.accounts.backends.EmailOrPhoneBackend')
    user.profile.accept_terms()
    messages.success(request, f'Welcome{" to The Reverse Times" if new_account else " back"}, {user.first_name or "reader"}!')
    nxt = data.get('next') or ''
    if not (nxt and url_has_allowed_host_and_scheme(nxt, allowed_hosts={request.get_host()}, require_https=request.is_secure())):
        nxt = reverse('account')
    return JsonResponse({'ok': True, 'redirect': nxt})
