"""
Lectura de la carga útil de una petición, venga como venga.

Inertia envía los formularios como JSON salvo que haya un archivo, en cuyo caso
usa multipart. Django rellena `request.POST` sólo en el segundo caso: con JSON
queda vacío y la vista concluye que no se mandó nada, que es un fallo
particularmente confuso porque el formulario se ve completo en pantalla.

Tener un único lector evita que cada vista resuelva esto a su manera, y sobre
todo evita que alguna se olvide.
"""

import json

from django.conf import settings
from django.http import HttpRequest


def payload(request: HttpRequest) -> dict:
    """Devuelve los campos enviados, sean JSON o datos de formulario."""
    if request.content_type and 'application/json' in request.content_type:
        try:
            data = json.loads(request.body or b'{}')
        except json.JSONDecodeError:
            return {}
        return data if isinstance(data, dict) else {}

    return request.POST.dict()


def field(request: HttpRequest, name: str, default: str = '') -> str:
    """Un campo de texto, ya normalizado a cadena."""
    value = payload(request).get(name, default)
    return value if isinstance(value, str) else str(value)


def field_list(request: HttpRequest, name: str) -> list[str]:
    """
    Un campo que trae varios valores, ya normalizado a lista de cadenas.

    Existe por lo mismo que `field`: con JSON los valores llegan como lista y con
    multipart como campo repetido, y una vista que lea sólo una de las dos formas
    anda hasta el día que alguien adjunta un archivo. Lo que no sea una cadena se
    descarta acá y no llega a la vista: una lista con un objeto adentro es un
    envío armado a mano, no una equivocación de quien mira la pantalla.
    """
    if request.content_type and 'application/json' in request.content_type:
        values = payload(request).get(name, [])
        if not isinstance(values, list):
            return []
        return [value for value in values if isinstance(value, str)]

    return request.POST.getlist(name)


def client_ip(request: HttpRequest) -> str:
    """
    Dirección desde la que llegó la petición, o cadena vacía si no se puede saber.

    Por omisión se usa `REMOTE_ADDR`, que es lo único que el cliente no puede
    inventar. `X-Forwarded-For` sólo se mira cuando la configuración declara que
    hay un proxy propio delante reescribiéndola: si se leyera siempre, cualquiera
    podría mandar la cabecera a mano y la lista de sesiones mostraría la
    dirección que el atacante quiera.
    """
    if getattr(settings, 'TRUST_FORWARDED_FOR', False):
        forwarded = request.META.get('HTTP_X_FORWARDED_FOR', '')
        first = forwarded.split(',')[0].strip()
        if first:
            return first[:45]

    return (request.META.get('REMOTE_ADDR') or '')[:45]


def user_agent(request: HttpRequest, max_length: int) -> str:
    """El agente declarado, recortado. Se guarda tal como llegó: no se interpreta acá."""
    return (request.META.get('HTTP_USER_AGENT') or '')[:max_length]
