"""
CMS Security Settings — 2FA setup and management.
All views require an authenticated CMS user.
"""
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required

from .decorators import cms_required
from .models import TwoFactorAuth
from .twofa import (
    generate_totp_secret, get_totp_qr_base64, verify_totp,
    send_email_otp, verify_email_otp,
    send_sms_otp, verify_sms_otp,
    generate_backup_codes, hash_backup_code,
)


@cms_required
def security_settings(request):
    user = request.user
    fa, _ = TwoFactorAuth.objects.get_or_create(user=user)

    ctx = {
        'fa': fa,
        'totp_qr': None,
        'totp_secret': None,
        'email_otp_sent': request.session.get('email_otp_sent', False),
        'sms_otp_sent': request.session.get('sms_otp_sent', False),
        'sms_phone': request.session.get('sms_phone', ''),
        'new_backup_codes': request.session.pop('new_backup_codes', None),
    }

    # If TOTP setup is in progress, restore QR from session
    if request.session.get('totp_secret_pending'):
        secret = request.session['totp_secret_pending']
        ctx['totp_qr'] = get_totp_qr_base64(secret, user.email)
        ctx['totp_secret'] = secret

    if request.method == 'POST':
        action = request.POST.get('action')

        # ── Setup TOTP: generate secret + QR ───────────────────────────────
        if action == 'setup_totp':
            secret = generate_totp_secret()
            request.session['totp_secret_pending'] = secret
            ctx['totp_qr'] = get_totp_qr_base64(secret, user.email)
            ctx['totp_secret'] = secret
            return render(request, 'cms/security.html', ctx)

        # ── Confirm TOTP: user entered code after scanning QR ──────────────
        elif action == 'confirm_totp':
            secret = request.POST.get('totp_secret') or request.session.get('totp_secret_pending', '')
            code = request.POST.get('totp_code', '').strip()
            if verify_totp(secret, code):
                fa.totp_secret = secret
                fa.totp_confirmed = True
                fa.is_enabled = True
                fa.preferred_method = TwoFactorAuth.METHOD_TOTP
                _ensure_backup_codes(fa)
                fa.save()
                request.session.pop('totp_secret_pending', None)
                messages.success(request, 'Authenticator app enrolled successfully. 2FA is now active.')
            else:
                messages.error(request, 'Invalid code. Please try again.')
                ctx['totp_qr'] = get_totp_qr_base64(secret, user.email)
                ctx['totp_secret'] = secret
            return render(request, 'cms/security.html', ctx)

        # ── Setup Email OTP ────────────────────────────────────────────────
        elif action == 'setup_email':
            send_email_otp(user, session=request.session)
            request.session['email_otp_sent'] = True
            messages.success(request, f'Verification code sent to {user.email}')
            return redirect('cms_security')

        # ── Confirm Email OTP ──────────────────────────────────────────────
        elif action == 'confirm_email':
            code = request.POST.get('email_code', '').strip()
            if verify_email_otp(user, code, session=request.session):
                fa.email_confirmed = True
                fa.is_enabled = True
                if not fa.preferred_method or fa.preferred_method not in fa.available_methods:
                    fa.preferred_method = TwoFactorAuth.METHOD_EMAIL
                _ensure_backup_codes(fa)
                fa.save()
                request.session.pop('email_otp_sent', None)
                messages.success(request, 'Email OTP enrolled. 2FA is now active.')
            else:
                messages.error(request, 'Invalid code. Please try again.')
            return redirect('cms_security')

        # ── Setup SMS OTP ──────────────────────────────────────────────────
        elif action == 'setup_sms':
            phone = request.POST.get('sms_phone', '').strip()
            if not phone:
                messages.error(request, 'Please enter a phone number.')
                return redirect('cms_security')
            fa.sms_phone = phone
            fa.save(update_fields=['sms_phone'])
            user.phone = phone
            user.save(update_fields=['phone'])
            ok = send_sms_otp(user, session=request.session)
            if ok:
                request.session['sms_otp_sent'] = True
                request.session['sms_phone'] = phone
                messages.success(request, f'Code sent to {phone}')
            else:
                messages.error(request, 'Could not send SMS. Check your phone number and try again.')
            return redirect('cms_security')

        # ── Confirm SMS OTP ────────────────────────────────────────────────
        elif action == 'confirm_sms':
            code = request.POST.get('sms_code', '').strip()
            if verify_sms_otp(user, code, session=request.session):
                fa.sms_confirmed = True
                fa.is_enabled = True
                if not fa.preferred_method or fa.preferred_method not in fa.available_methods:
                    fa.preferred_method = TwoFactorAuth.METHOD_SMS
                _ensure_backup_codes(fa)
                fa.save()
                request.session.pop('sms_otp_sent', None)
                request.session.pop('sms_phone', None)
                messages.success(request, 'SMS OTP enrolled. 2FA is now active.')
            else:
                messages.error(request, 'Invalid code. Please try again.')
            return redirect('cms_security')

        # ── Set preferred method ───────────────────────────────────────────
        elif action == 'set_preferred':
            method = request.POST.get('method')
            if method in fa.available_methods:
                fa.preferred_method = method
                fa.save(update_fields=['preferred_method'])
                messages.success(request, f'{dict(TwoFactorAuth.METHOD_CHOICES)[method]} set as your default 2FA method.')
            return redirect('cms_security')

        # ── Regenerate backup codes ────────────────────────────────────────
        elif action == 'regenerate_backup_codes':
            plain_codes = generate_backup_codes(8)
            fa.backup_codes_list = [{'hash': hash_backup_code(c), 'used': False} for c in plain_codes]
            fa.save(update_fields=['backup_codes'])
            request.session['new_backup_codes'] = plain_codes
            messages.success(request, 'New backup codes generated. Save them now.')
            return redirect('cms_security')

        # ── Disable 2FA ────────────────────────────────────────────────────
        elif action == 'disable_2fa':
            fa.is_enabled = False
            fa.totp_confirmed = False
            fa.totp_secret = ''
            fa.email_confirmed = False
            fa.sms_confirmed = False
            fa.sms_phone = ''
            fa.backup_codes = '[]'
            fa.save()
            messages.success(request, '2FA has been disabled. Your account is now less secure.')
            return redirect('cms_security')

    return render(request, 'cms/security.html', ctx)


def _ensure_backup_codes(fa):
    """Generate backup codes if none exist yet."""
    if not fa.backup_codes_list:
        plain_codes = generate_backup_codes(8)
        fa.backup_codes_list = [{'hash': hash_backup_code(c), 'used': False} for c in plain_codes]
