"""
Vistas de la interfaz.

Arman props y delegan: ninguna lleva lógica de negocio propia. La regla no es
estética —es lo que mantiene la paridad entre la interfaz y la API pública. Si
una decisión vive en la vista, la API no la tiene, y el producto empieza a
comportarse distinto según por dónde se lo use (principio IV).
"""

from django.contrib.auth import authenticate
from django.contrib.auth import login as django_login
from django.contrib.auth import logout as django_logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from django.urls import reverse
from django.views.decorators.http import require_http_methods
from inertia import render

from apps.core.errors import stash_errors
from apps.core.requests import field, payload
from apps.onboarding.services import should_offer
from apps.web.dashboard import RANGES, dashboard, has_domains
from apps.web.tools import available_tools


@require_http_methods(['GET', 'POST'])
def login(request):
    """
    Ingreso con correo y contraseña, en `/login` y sin prefijo de agrupación.

    El fallo no distingue entre «ese correo no existe» y «la contraseña no es
    ésa»: separarlos convierte el formulario en una manera de averiguar qué
    correos están registrados.
    """
    if request.method == 'POST':
        return _process_login(request)

    if request.user.is_authenticated:
        return redirect('home')

    # El destino viaja como prop y la página lo reenvía como campo del
    # formulario. No alcanza con que esté en la dirección: el formulario postea
    # a «/login» a secas, sin la cadena de consulta, así que por ese camino el
    # destino se pierde y quien entró por un enlace termina en el listado.
    return render(request, 'Login', props={'next': _safe_destination(request) or ''})


def _process_login(request):
    email = field(request, 'email').strip()
    password = field(request, 'password')

    errors = {}
    if not email:
        errors['email'] = {'code': 'EMAIL_REQUIRED'}
    if not password:
        errors['password'] = {'code': 'PASSWORD_REQUIRED'}

    # Ante un error se vuelve al formulario conservando el destino: perderlo
    # convertiría una contraseña mal escrita en la pérdida del enlace por el que
    # la persona había llegado.
    destination = _safe_destination(request)
    back = f'{reverse("login")}?next={destination}' if destination else reverse('login')

    if errors:
        stash_errors(request, errors)
        return redirect(back)

    account = authenticate(request, username=email, password=password)
    if account is None:
        # Va como error **del formulario** y no de un campo. El mensaje habla de
        # los dos —«el correo o la contraseña»— y colgarlo de uno solo marca como
        # inválido un campo que puede estar perfecto, justo en la única pantalla
        # donde no podemos decir cuál de los dos falló (RT-08).
        stash_errors(request, {'form': {'code': 'CREDENTIALS_INVALID'}})
        return redirect(back)

    if not account.is_active:
        stash_errors(request, {'email': {'code': 'ACCOUNT_DISABLED'}})
        return redirect(back)

    django_login(request, account)
    # Sin destino explícito se pasa por la raíz en vez de ir derecho al listado:
    # ahí es donde se decide si corresponde ofrecer el recorrido, y decidirlo
    # también acá haría que entrar por un enlace y entrar por el formulario
    # llevaran a pantallas distintas el primer día.
    return redirect(destination or reverse('home'))


def _safe_destination(request) -> str | None:
    """
    Devuelve el destino pedido sólo si es interno.

    Un `next` que apunta afuera convierte la pantalla de ingreso en un trampolín
    hacia un sitio ajeno con la apariencia de venir del nuestro.
    """
    destination = payload(request).get('next') or request.GET.get('next')
    if not destination:
        return None
    if destination.startswith('/') and not destination.startswith('//'):
        return destination
    return None


@require_http_methods(['POST'])
def logout(request):
    """
    Cierre de sesión por envío de formulario.

    Nunca por GET: un enlace o una imagen alojada en otro sitio pueden disparar
    un GET sin que nadie lo pida, y cerrarle la sesión a alguien a distancia es
    molesto aunque no sea peligroso.
    """
    django_logout(request)
    return redirect('login')


@login_required
def home(request):
    """
    La raíz: con qué herramienta se va a trabajar.

    Hasta acá la raíz era el tablero, porque había una sola herramienta y el
    tablero era el de esa. Ahora la plataforma ofrece varias y la raíz es donde
    se elige: el tablero se mudó adentro de Search Console, que es de quien es.

    **No redirige al recorrido guiado**, y ése es el cambio que importa. El
    recorrido conecta *una* herramienta, así que la raíz no puede dispararlo sin
    haber decidido antes cuál —y decidir cuál es exactamente lo que esta pantalla
    le deja a la persona—. Esa redirección ahora vive en la raíz de la
    herramienta, que sí sabe de qué conexión habla.
    """
    return render(request, 'Tools', props={'tools': available_tools(request.user)})


@login_required
def search_console(request):
    """
    El tablero de Search Console (V0).

    Antes redirigía al listado de dominios, y eso no era una pantalla de inicio
    sino un inventario: para saber si algo andaba mal había que entrar dominio
    por dominio. El tablero contesta esa pregunta arriba de todo y con una frase.

    Sigue habiendo una redirección, pero una sola y con motivo: quien todavía no
    dejó su cuenta conectada no tiene nada que mirar acá —ni cobertura, ni cupo,
    ni lotes—, así que entra por el recorrido. Se ofrece, no se impone: omitirlo
    no restringe nada (FR-018) y a partir de ahí ésta es la pantalla de entrada
    de la herramienta.
    """
    if should_offer(request.user):
        return redirect('onboarding')

    days = _chart_range(request)

    return render(
        request,
        'Dashboard',
        props={
            # Qué panel está abierto sale de la dirección, que es donde vive
            # (`?panel=coverage`). Abrirlo es una navegación, así que el servidor
            # se entera y puede mandar el detalle sólo entonces.
            #
            # `request.GET` y no `field()`: esto viaja en la cadena de consulta,
            # que Django parsea siempre, y `field()` lee el cuerpo. Es la misma
            # distinción que hace `_chart_range` con `?days=`.
            **dashboard(request.user, days=days, open_panel=request.GET.get('panel', '')),
            # `active_work` no va acá: viaja como prop compartida, porque el
            # indicador de actividad vive en la barra superior y se dibuja en
            # todas las pantallas (D9, D10).
            'has_domains': has_domains(request.user),
        },
    )


def _chart_range(request) -> int:
    """
    Cuántos días dibuja el gráfico, según la dirección.

    Va en la cadena de consulta y no en el estado del componente para que el
    rango elegido sobreviva a recargar y se pueda compartir el enlace (RT-09).
    Un valor que no está en la lista se ignora en silencio: es una dirección mal
    escrita, no algo que la persona pueda corregir leyendo un mensaje.
    """
    try:
        requested = int(request.GET.get('days', ''))
    except ValueError:
        return RANGES[1]

    return requested if requested in RANGES else RANGES[1]
