from ._base import *


class LoginForm(forms.Form):
    email = forms.EmailField(max_length=320, required=True)
    password = forms.CharField(widget=forms.PasswordInput, required=True)
def sha1(data):
    """Función auxiliar para calcular SHA-1."""
    return hashlib.sha1(data).digest()
def calculate_srp6_verifier(username, password, salt):
    """Calcula el verifier usando SRP6 basado en la implementación PHP."""
    g = gmpy2.mpz(7)
    N = gmpy2.mpz('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16)

    # Paso 1: Calcular H(username:password)
    h1 = sha1((username.upper() + ':' + password.upper()).encode('utf-8'))

    # Usar el salt proporcionado (sin invertir)
    salt_bytes = bytes.fromhex(salt)

    # Paso 2: Calcular H(salt + H(username:password))
    h2 = sha1(salt_bytes + h1)

    # Convertir el hash a un entero (little-endian)
    h2_int = gmpy2.mpz.from_bytes(h2, 'little')

    # Calcular el verifier usando g^h2 mod N
    verifier = gmpy2.powmod(g, h2_int, N)
    verifier_bytes = verifier.to_bytes(32, 'little')

    # Convertir el resultado a hexadecimal en mayúsculas
    return binascii.hexlify(verifier_bytes).decode('utf-8').upper()
def authenticate(email, password):
    """Autentica una cuenta Battle.net por email (SRP6 v2)."""
    try:
        with connections['acore_auth'].cursor() as cursor:
            account = bnet.get_bnet_account_by_email(cursor, email)
        if not account:
            return None
        if bnet.bnet_verify(account['email'], password, account['salt'], account['verifier']):
            return account
        return None
    except Exception:
        return None
def _set_game_account_session(request, game_account):
    """Fija en sesión la cuenta de juego seleccionada (para las demás vistas)."""
    request.session['username'] = game_account['username'].upper()
    request.session['account_id'] = game_account['id']
def login_view(request):
    if request.method == 'POST':
        if not verify_turnstile(request):
            return JsonResponse({'success': False, 'error': 'Verificación anti-bots fallida. Reintenta.'})
        form = LoginForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data['email']
            password = form.cleaned_data['password']
            ip_address = request.META.get('REMOTE_ADDR')

            account = authenticate(email, password)
            if account:
                log_login_attempt(email, "Exito", ip_address)

                # Guardar la identidad Battle.net en la sesión
                request.session['bnet_id'] = account['id']
                request.session['bnet_email'] = account['email']

                # Resolver las cuentas de juego enlazadas
                with connections['acore_auth'].cursor() as cursor:
                    game_accounts = bnet.get_game_accounts_for_bnet(cursor, account['id'])

                if len(game_accounts) == 1:
                    # Una sola cuenta de juego: seleccionar automáticamente
                    _set_game_account_session(request, game_accounts[0])
                elif len(game_accounts) > 1:
                    # Varias: dejar la selección pendiente (my-account redirige al selector)
                    request.session.pop('username', None)
                    request.session.pop('account_id', None)
                # 0 cuentas: se queda sin username; el selector mostrará el aviso

                return JsonResponse({'success': True})
            else:
                log_login_attempt(email, "Fracaso", ip_address)
                return JsonResponse({'success': False, 'error': "Correo o contraseña incorrectos"})
        else:
            return JsonResponse({'success': False, 'error': "Formulario no valido"})

    form = LoginForm()
    return render(request, 'auth/login.html', {'form': form})
def select_account_view(request):
    """Selector de cuenta de juego para cuentas Battle.net con varias."""
    bnet_id = request.session.get('bnet_id')
    if not bnet_id:
        return redirect('login')

    with connections['acore_auth'].cursor() as cursor:
        game_accounts = bnet.get_game_accounts_for_bnet(cursor, bnet_id)

    # La selección la hace la isla React vía GET (/api/account/select/), sin POST.

    # Autoseleccionar si solo hay una
    if len(game_accounts) == 1:
        _set_game_account_session(request, game_accounts[0])
        return redirect('my-account')

    return render(request, 'auth/select_account.html', {
        'game_accounts': game_accounts,
        'bnet_email': request.session.get('bnet_email'),
    })
def logout_view(request):
    for key in ('username', 'account_id', 'bnet_id', 'bnet_email'):
        request.session.pop(key, None)
    return redirect('index')
def register_view(request):
    if request.method == 'POST':
        if not verify_turnstile(request):
            return JsonResponse({'success': False, 'message': 'Verificación anti-bots fallida. Reintenta.'})
        password = request.POST.get('password', '').strip()
        conf_password = request.POST.get('conf-password', '').strip()
        email = request.POST.get('email', '').strip()
        conf_email = request.POST.get('conf-email', '').strip()
        recruiter = request.POST.get('recruiter', '').strip()

        # En 3.4.3 la identidad es el email (cuenta Battle.net); no hay username.
        if not password or not email:
            return JsonResponse({'success': False, 'message': 'Por favor, complete todos los campos.'})

        if password != conf_password:
            return JsonResponse({'success': False, 'message': 'Las contraseñas no coinciden.'})

        if len(password) > 16:
            return JsonResponse({'success': False, 'message': 'La contraseña no debe exceder los 16 caracteres.'})

        if email != conf_email or not re.match(r'^[a-zA-Z0-9._%+-]+@gmail\.com$', email):
            return JsonResponse({'success': False, 'message': 'El correo electrónico no es válido.'})

        # Verificar si ya existe una cuenta Battle.net con ese email
        email_norm = bnet.normalize_email(email)
        with connections['acore_auth'].cursor() as cursor:
            cursor.execute("SELECT COUNT(*) FROM battlenet_accounts WHERE email = %s", [email_norm])
            if cursor.fetchone()[0] > 0:
                return JsonResponse({'success': False, 'message': 'Ya existe una cuenta con ese correo electrónico.'})

        # Comprobar que no haya un registro de activación pendiente para ese email
        if AccountActivation.objects.filter(email=email, old_email__isnull=True).exists():
            AccountActivation.objects.filter(email=email, old_email__isnull=True).delete()

        # Validar y obtener recruiter_id (id de cuenta de juego del reclutador)
        recruiter_id = 0
        if recruiter:
            with connections['acore_auth'].cursor() as cursor:
                cursor.execute("SELECT id FROM account WHERE username = %s", [recruiter])
                recruiter_data = cursor.fetchone()
                if recruiter_data:
                    recruiter_id = recruiter_data[0]
                else:
                    with connections['acore_characters'].cursor() as char_cursor:
                        char_cursor.execute("SELECT account FROM characters WHERE name = %s", [recruiter])
                        character_data = char_cursor.fetchone()
                        if character_data:
                            recruiter_id = character_data[0]
                        else:
                            return JsonResponse({'success': False, 'message': 'El reclutador ingresado no existe.'})

        # Generar un hash único para activación. Los salt/verifier se calculan al
        # activar (tanto el de bnet como el de la cuenta de juego).
        activation_hash = get_random_string(32)

        AccountActivation.objects.create(
            email=email,
            password=password,
            recruiter_id=recruiter_id,
            hash=activation_hash
        )

        # Enviar correo de activación
        activation_link = f"{settings.URL_PRINCIPAL}/es/activate-account?act={activation_hash}"
        context = {
            'username': email,
            'password': password,
            'activation_link': activation_link,
            'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
        }
        enviar_correo(
            subject=f'Activación de la cuenta {email} - {settings.NOMBRE_SERVIDOR}',
            to_email=email,
            template='emails/activation.html',
            context=context
        )

        message = f"""
        <div class="alert-message" id="create-response" style="display: block;">
            <span class="ok-form-response">La cuenta '{email}' ha sido creada.</span><br><br>
            <span>Se ha enviado un enlace de activación al correo {email}.</span>
        </div>
        """
        return JsonResponse({'success': True, 'message': message})

    return render(request, 'auth/register.html')
def activate_account_view(request):
    activation_hash = request.GET.get('act')

    try:
        activation = AccountActivation.objects.get(hash=activation_hash)

        if activation.is_expired():
            return render(request, 'auth/activation_invalid.html')

        email_norm = bnet.normalize_email(activation.email)
        password = activation.password
        last_ip = request.META.get('REMOTE_ADDR')

        with connections['acore_auth'].cursor() as cursor:
            # Evitar duplicados si el enlace se usa dos veces
            cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [email_norm])
            existing = cursor.fetchone()
            if existing:
                activation.delete()
                return render(request, 'auth/activation_success.html')

            # 1) Crear la cuenta Battle.net (SRP6 v2)
            bnet_salt, bnet_verifier, srp_version = bnet.bnet_make_registration(email_norm, password)
            cursor.execute(
                "INSERT INTO battlenet_accounts (email, srp_version, salt, verifier) "
                "VALUES (%s, %s, %s, %s)",
                [email_norm, srp_version, bnet_salt, bnet_verifier]
            )

            # 2) Recuperar el id de la cuenta bnet recién creada
            cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [email_norm])
            bnet_id = cursor.fetchone()[0]

            # 3) Crear la cuenta de juego enlazada (<bnetId>#1, SRP6 Grunt)
            game_username = bnet.make_game_account_username(bnet_id, 1)
            game_salt, game_verifier = bnet.game_make_registration(game_username, password)
            cursor.execute(
                "INSERT INTO account "
                "(username, salt, verifier, email, reg_mail, recruiter, joindate, "
                " last_ip, expansion, battlenet_account, battlenet_index) "
                "VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, %s, %s, %s)",
                [
                    game_username, game_salt, game_verifier,
                    email_norm, email_norm, activation.recruiter_id or 0,
                    last_ip, 2, bnet_id, 1
                ]
            )

        activation.delete()
        return render(request, 'auth/activation_success.html')

    except AccountActivation.DoesNotExist:
        return render(request, 'auth/activation_invalid.html')
def recover_account_view(request):
    """Recuperación de cuenta (Battle.net, por email). 3 tipos:
    password (enlace de reset), accountname (lista de cuentas), activation (reenvío).
    Respuestas genéricas para no revelar si el correo existe (anti-enumeración).
    """
    if request.method == 'POST':
        if not verify_turnstile(request):
            return JsonResponse({'success': False, 'message': 'Verificación anti-bots fallida. Reintenta.'})
        rtype = request.POST.get('type', '').strip()
        email = request.POST.get('email', '').strip()
        if not email or not re.match(r'^[a-zA-Z0-9._%+-]+@gmail\.com$', email):
            return JsonResponse({'success': False, 'message': 'Introduce un correo de Gmail válido.'})

        if rtype == 'password':
            try:
                with connections['acore_auth'].cursor() as cursor:
                    account = bnet.get_bnet_account_by_email(cursor, email)
            except Exception as e:
                logger.warning("recover(password): BD de cuentas no disponible: %s", e)
                account = None
            if account:
                token = get_random_string(64)
                PasswordReset.objects.create(email=email, token=token)
                reset_link = f"{settings.URL_PRINCIPAL}/es/reset-password?token={token}"
                enviar_correo(
                    subject=f'Restablecer contraseña - {settings.NOMBRE_SERVIDOR}',
                    to_email=email,
                    template='emails/password_reset.html',
                    context={'reset_link': reset_link, 'email': email,
                             'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR,
                             'URL_PRINCIPAL': settings.URL_PRINCIPAL},
                )
            return JsonResponse({'success': True, 'message': 'Si existe una cuenta con ese correo, te hemos enviado un enlace para restablecer la contraseña.'})

        if rtype == 'accountname':
            try:
                with connections['acore_auth'].cursor() as cursor:
                    account = bnet.get_bnet_account_by_email(cursor, email)
                    game_accounts = bnet.get_game_accounts_for_bnet(cursor, account['id']) if account else []
            except Exception as e:
                logger.warning("recover(accountname): BD de cuentas no disponible: %s", e)
                account, game_accounts = None, []
            if account:
                enviar_correo(
                    subject=f'Tus cuentas de juego - {settings.NOMBRE_SERVIDOR}',
                    to_email=email,
                    template='emails/account_names.html',
                    context={'email': email, 'accounts': game_accounts,
                             'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR,
                             'URL_PRINCIPAL': settings.URL_PRINCIPAL},
                )
            return JsonResponse({'success': True, 'message': 'Si existe una cuenta con ese correo, te hemos enviado la lista de cuentas asociadas.'})

        if rtype == 'activation':
            activation = AccountActivation.objects.filter(
                email=email, old_email__isnull=True).order_by('-created_at').first()
            if activation:
                activation_link = f"{settings.URL_PRINCIPAL}/es/activate-account?act={activation.hash}"
                enviar_correo(
                    subject=f'Activación de la cuenta {email} - {settings.NOMBRE_SERVIDOR}',
                    to_email=email,
                    template='emails/activation.html',
                    context={'username': email, 'password': activation.password,
                             'activation_link': activation_link,
                             'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR},
                )
            return JsonResponse({'success': True, 'message': 'Si hay una activación pendiente para ese correo, te hemos reenviado el enlace.'})

        return JsonResponse({'success': False, 'message': 'Opción no válida.'})

    return render(request, 'auth/recover_account.html')


def reset_password_view(request):
    """Fija una nueva contraseña a partir de un token de PasswordReset (por email).
    Re-deriva el verifier SRP6 v2 de la cuenta Battle.net (igual que change_password)."""
    if request.method == 'POST':
        token = request.POST.get('token', '').strip()
        new_password = request.POST.get('new-password', '').strip()
        conf_password = request.POST.get('conf-password', '').strip()

        pr = PasswordReset.objects.filter(token=token, used=False).first()
        if not pr or pr.is_expired():
            return JsonResponse({'success': False, 'message': 'El enlace no es válido o ha caducado.'})
        if not new_password or new_password != conf_password:
            return JsonResponse({'success': False, 'message': 'Las contraseñas no coinciden.'})
        if len(new_password) > 16:
            return JsonResponse({'success': False, 'message': 'La contraseña no debe exceder los 16 caracteres.'})

        new_salt, new_verifier, srp_version = bnet.bnet_make_registration(pr.email, new_password)
        try:
            with connections['acore_auth'].cursor() as cursor:
                account = bnet.get_bnet_account_by_email(cursor, pr.email)
                if not account:
                    return JsonResponse({'success': False, 'message': 'No se ha encontrado la cuenta.'})
                cursor.execute(
                    "UPDATE battlenet_accounts SET srp_version = %s, salt = %s, verifier = %s WHERE id = %s",
                    [srp_version, new_salt, new_verifier, account['id']]
                )
        except Exception as e:
            logger.error("reset_password: error al actualizar la cuenta: %s", e)
            return JsonResponse({'success': False, 'message': 'No se ha podido actualizar la contraseña. Inténtalo más tarde.'})
        pr.used = True
        pr.save(update_fields=['used'])
        return JsonResponse({'success': True,
                             'message': 'Contraseña restablecida. Ya puedes iniciar sesión.',
                             'redirect': reverse('login')})

    token = request.GET.get('token', '').strip()
    pr = PasswordReset.objects.filter(token=token, used=False).first()
    valid = bool(pr and not pr.is_expired())
    return render(request, 'auth/reset_password.html', {'token': token, 'valid': valid})
def change_password_view(request):
    # Verificar si el usuario está autenticado mediante la sesión
    username = request.session.get('username')
    if not username:
        return redirect('login')

    # Obtener el usuario desde la base de datos de `acore_auth`
    user_data = get_user_from_acore(username)
    if not user_data:
        return redirect('login')

    user_id = user_data['id']
    email = user_data['email']

    if request.method == 'POST':
        current_password = request.POST.get('cur-password', '').strip()
        new_password = request.POST.get('new-password', '').strip()
        conf_new_password = request.POST.get('conf-new-password', '').strip()
        security_token = request.POST.get('security-token', '').strip()

        # Validar campos vacíos
        if not current_password or not new_password or not conf_new_password or not security_token:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, complete todos los campos.</span>'})

        # Validar que la nueva contraseña coincida con su confirmación
        if new_password != conf_new_password:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">Las contraseñas no coinciden.</span>'})

        # Validar longitud de la nueva contraseña
        if len(new_password) > 16:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña nueva no debe exceder los 16 caracteres.</span>'})

        # Verificar si el token de seguridad ha sido generado
        existing_token = SecurityToken.objects.filter(user_id=user_id).first()
        if not existing_token:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">No tienes un token de seguridad generado. Por favor, genera uno antes de cambiar tu contraseña.</span>'})

        # Verificar si el token proporcionado es correcto
        if existing_token.token != security_token:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">El token ingresado es incorrecto.</span>'})

        # Verificar la contraseña actual contra la cuenta Battle.net (por email)
        if not authenticate(email, current_password):
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña actual es incorrecta.</span>'})

        # Generar salt y verifier SRP6 v2 para la nueva contraseña (bnet)
        new_salt_bytes, new_verifier_bytes, srp_version = bnet.bnet_make_registration(email, new_password)

        # Localizar la cuenta bnet (por sesión o por email) y actualizarla
        bnet_id = request.session.get('bnet_id')
        with connections['acore_auth'].cursor() as cursor:
            if not bnet_id:
                account = bnet.get_bnet_account_by_email(cursor, email)
                bnet_id = account['id'] if account else None
            if not bnet_id:
                return JsonResponse({'success': False, 'message': '<span class="red-form-response">No se ha encontrado la cuenta.</span>'})
            cursor.execute(
                "UPDATE battlenet_accounts SET srp_version = %s, salt = %s, verifier = %s WHERE id = %s",
                [srp_version, new_salt_bytes, new_verifier_bytes, bnet_id]
            )

        # Cerrar la sesión por seguridad
        for key in ('username', 'account_id', 'bnet_id', 'bnet_email'):
            request.session.pop(key, None)

        # Redirigir al usuario a la página de inicio de sesión
        return JsonResponse({
            'success': True,
            'message': '<span class="ok-form-response">Contraseña cambiada exitosamente. Has sido desconectado por seguridad.</span>',
            'redirect': True
        })

    return render(request, 'account/change_password.html')
def get_user_from_acore(username):
    """
    Obtiene la información del usuario desde la base de datos `acore_auth`.
    """
    with connections['acore_auth'].cursor() as cursor:
        cursor.execute("SELECT id, email FROM account WHERE username = %s", [username])
        result = cursor.fetchone()
        if result:
            return {'id': result[0], 'email': result[1]}
    return None
def security_token_view(request):
    # Verificar si el usuario está autenticado mediante la sesión
    username = request.session.get('username')
    if not username:
        return redirect('index')

    # Obtener el usuario desde la base de datos de `acore_auth`
    user_data = get_user_from_acore(username)
    if not user_data:
        return redirect('index')

    user_id = user_data['id']
    email = user_data['email']
    ip_address = request.META.get('REMOTE_ADDR')

    # Verificar si existe un token activo para mostrar la fecha en la plantilla
    existing_token = SecurityToken.objects.filter(user_id=user_id).first()
    
    # Obtener la fecha del token o "Sin solicitar" si no hay token
    token_date = "Sin solicitar"
    if existing_token:
        token_date = existing_token.created_at.strftime('%H:%M:%S %d-%m-%Y')

    if request.method == 'POST':
        # Verificar si el correo del usuario está vacío
        if not email:
            # Si no tiene correo, devolver un mensaje de error en formato JSON
            return JsonResponse({
                'success': False,
                'message': f'<span class="red-form-response">Debes añadir un correo electrónico a tu cuenta antes de solicitar un Token de seguridad.</span>'
            })
        # Verificar si ya existe un token y si se puede generar uno nuevo después de 7 días
        if existing_token:
            # Comprobar si han pasado al menos 7 días desde la creación del token
            days_since_creation = (timezone.now() - existing_token.created_at).days
            if days_since_creation < 7:
                remaining_days = 7 - days_since_creation
                return JsonResponse({
                    'success': False,
                    'message': f'<span class="red-form-response">Sólo puedes solicitar un nuevo Token de seguridad cada 7 días, faltan {remaining_days} días para generar un nuevo Token.</span>'
                })

        # Generar un nuevo token
        token = secrets.token_urlsafe(4)[:6]
        expires_at = timezone.now() + timedelta(days=7)

        # Eliminar el token anterior y crear uno nuevo
        if existing_token:
            existing_token.delete()

        # Crear y guardar el nuevo token
        new_token = SecurityToken.objects.create(
            user_id=user_id,
            token=token,
            ip_address=ip_address,
            expires_at=expires_at  # Esta fecha solo se usa para el control de los 7 días
        )

        # Enviar el correo con el token al usuario
        send_security_token_email(email, username, token, ip_address)

        # Obtener la nueva fecha del token
        new_token_date = new_token.created_at.strftime('%H:%M:%S %d-%m-%Y')

        # Responder con un mensaje exitoso y la nueva fecha
        return JsonResponse({
            'success': True,
            'message': f'<span class="ok-form-response">Se ha enviado el Token de seguridad al correo {email}.</span>',
            'token_date': new_token_date
        })

    # Pasar `token_date` al contexto de la plantilla
    return render(request, 'account/security_token.html', {
        'token_date': token_date
    })
def send_security_token_email(email, username, token, ip_address):
    """
    Envía un correo electrónico con el token de seguridad.
    """
    context = {
        'username': username,
        'token': token,
        'ip_address': ip_address,
        'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
    }

    # Llamada a tu función personalizada `enviar_correo`
    enviar_correo(
         subject=f'Token de seguridad de la cuenta {username} - {settings.NOMBRE_SERVIDOR}',
        to_email=email,
        template='emails/security_token.html',  # Asegúrate de que este template exista
        context=context
    )
def change_email_view(request):
    username = request.session.get('username')
    if not username:
        return redirect('login')

    # Obtener el usuario desde la base de datos de `acore_auth`
    with connections['acore_auth'].cursor() as cursor:
        cursor.execute("""
            SELECT id, email, reg_mail
            FROM account 
            WHERE username = %s
        """, [username])
        account_data = cursor.fetchone()

    if not account_data:
        return redirect('login')

    user_id, current_email, reg_email = account_data
    security_token = SecurityToken.objects.filter(user_id=user_id).first()

    if request.method == 'POST':
        print("Datos recibidos:", request.POST)

        current_password = request.POST.get('cur-password', '').strip()
        current_email_input = request.POST.get('cur-email', '').strip()
        new_email = request.POST.get('new-email', '').strip()
        conf_new_email = request.POST.get('conf-new-email', '').strip()
        token = request.POST.get('security-token', '').strip()

        # Validar los campos
        if not all([current_password, current_email_input, new_email, conf_new_email, token]):
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, complete todos los campos.</span>'})

        if current_email_input != current_email:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">El correo actual no es correcto.</span>'})

        if new_email != conf_new_email:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">Los correos no coinciden.</span>'})

        if not re.match(r'^[a-zA-Z0-9._%+-]+@gmail\.com$', new_email):
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">El nuevo correo no es válido.</span>'})

        if not security_token or security_token.token != token:
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">Token de seguridad incorrecto.</span>'})

        # Verificar la contraseña contra la cuenta Battle.net (por email actual)
        if not authenticate(current_email, current_password):
            return JsonResponse({'success': False, 'message': '<span class="red-form-response">Contraseña incorrecta.</span>'})

        # Generar hashes
        old_email_hash = secrets.token_urlsafe(16)
        new_email_hash = secrets.token_urlsafe(16)

        # Guardar en `AccountActivation`. Se conserva la contraseña porque al
        # cambiar el email hay que recalcular el verifier de bnet (el usuario SRP
        # depende del email).
        AccountActivation.objects.create(
            username=username,
            email=new_email,
            old_email=current_email,
            password=current_password,
            recruiter_id=user_id,
            hash=new_email_hash,
            old_email_hash=old_email_hash
        )

        # Enviar confirmación al correo actual
        confirm_old_email_link = f"{settings.URL_PRINCIPAL}/es/confirm-old-email?hash={old_email_hash}"
        context_old_email = {
            'username': username,
            'confirm_link': confirm_old_email_link,
            'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
        }
        enviar_correo(
            subject=f'Confirme el cambio de correo para {username}',
            to_email=current_email,
            template='emails/confirm_old_email.html',
            context=context_old_email
        )

        return JsonResponse({'success': True, 'message': '<span class="ok-form-response">Se ha enviado un correo de confirmación al correo actual.</span>'})

    return render(request, 'account/change_email.html')
def confirm_old_email_view(request):
    hash = request.GET.get('hash')
    activation = AccountActivation.objects.filter(old_email_hash=hash, is_used=False).first()

    if not activation or activation.is_expired():
        return JsonResponse({
            'success': False,
            'message': '<span class="red-form-response">Enlace no válido o expirado.</span>',
            'redirect_url': reverse('expired-link')
        })

    # Marcar solo el old_email_hash como usado
    activation.is_used = True
    activation.save()

    # Enviar correo al nuevo correo para confirmar el cambio
    activation_link = f"{settings.URL_PRINCIPAL}/es/confirm-new-email?hash={activation.hash}"
    context_new_email = {
        'username': activation.username,
        'activation_link': activation_link,
        'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
    }
    enviar_correo(
        subject=f'Confirme su nuevo correo para {activation.username}',
        to_email=activation.email,
        template='emails/confirm_new_email.html',
        context=context_new_email
    )

    return JsonResponse({'success': True, 'message': '<span class="ok-form-response">Correo confirmado. Se ha enviado un enlace al nuevo correo.</span>'})
def confirm_new_email_view(request):
    hash = request.GET.get('hash')
    activation = AccountActivation.objects.filter(hash=hash, is_new_email_used=False).first()

    if not activation or activation.is_expired():
        return JsonResponse({
            'success': False,
            'message': '<span class="red-form-response">Enlace no válido o expirado.</span>',
            'redirect_url': reverse('expired-link')
        })

    # Marcar solo el new_email_hash como usado
    activation.is_new_email_used = True
    activation.save()

    # Cambiar el email en la cuenta Battle.net implica recalcular salt/verifier,
    # porque el usuario SRP = SHA256(UPPER(email)).
    new_email_norm = bnet.normalize_email(activation.email)
    old_email_norm = bnet.normalize_email(activation.old_email or '')
    new_salt, new_verifier, srp_version = bnet.bnet_make_registration(new_email_norm, activation.password)

    with connections['acore_auth'].cursor() as cursor:
        # Localizar la cuenta bnet por su email actual (el antiguo)
        cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [old_email_norm])
        row = cursor.fetchone()
        if not row:
            return JsonResponse({
                'success': False,
                'message': '<span class="red-form-response">No se ha encontrado la cuenta Battle.net.</span>',
            })
        bnet_id = row[0]

        # Actualizar email + verifier en battlenet_accounts
        cursor.execute(
            "UPDATE battlenet_accounts SET email = %s, srp_version = %s, salt = %s, verifier = %s WHERE id = %s",
            [new_email_norm, srp_version, new_salt, new_verifier, bnet_id]
        )
        # Actualizar el email de las cuentas de juego enlazadas (cosmético)
        cursor.execute(
            "UPDATE account SET email = %s, reg_mail = %s WHERE battlenet_account = %s",
            [new_email_norm, new_email_norm, bnet_id]
        )

    # Enviar notificación al correo anterior
    context_old_email = {
        'username': activation.username,
        'new_email': activation.email,
        'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
    }
    enviar_correo(
        subject=f'Su correo ha sido cambiado en {settings.NOMBRE_SERVIDOR}',
        to_email=activation.old_email,
        template='emails/old_email_notification.html',
        context=context_old_email
    )

    return JsonResponse({'success': True, 'message': '<span class="ok-form-response">El cambio de correo ha sido confirmado y completado con éxito.</span>'})
def expired_link_view(request):
    return render(request, 'account/expired_link.html')
def log_login_attempt(username, status, ip_address):
    # Registrar intento de inicio de sesión en el historial
    LoginAttempt.objects.create(
        username=username,
        status=status,
        ip_address=ip_address,
        timestamp=timezone.now()
    )
