"""Pruebas del cliente HTTP hacia EFC.

**Existen porque el carril de referencia no las tiene.** Verificado: en el gateway de Anexo22 no hay
ni una prueba de ``EfcClient._request``, así que su bucle de reintentos, su backoff, su corte en 4xx
y su header nunca se ejercitan. Ese hueco no se clona.

Todo va contra ``httpx.MockTransport`` por el parámetro ``transport``, que existe justamente para
esto: **ninguna de estas pruebas toca la red**.
"""

import httpx
import pytest

from core.efc_client import EfcClient, EfcClientError

BASE = "https://efc.example.test"
KEY = "llave-de-prueba"


def _client(handler, **kwargs) -> EfcClient:
    return EfcClient(
        base_url=kwargs.pop("base_url", BASE),
        api_key=kwargs.pop("api_key", KEY),
        timeout_ms=kwargs.pop("timeout_ms", 500),
        upload_timeout_ms=kwargs.pop("upload_timeout_ms", 500),
        verify_ssl=False,
        transport=httpx.MockTransport(handler),
        **kwargs,
    )


def test_reintenta_un_500_y_devuelve_el_exito():
    intentos = {"n": 0}

    def handler(request):
        intentos["n"] += 1
        if intentos["n"] == 1:
            return httpx.Response(500, json={"detail": "boom"})
        return httpx.Response(200, json={"id": "org-1"})

    resp = _client(handler).resolve_organizacion("temex")
    assert resp == {"id": "org-1"}
    assert intentos["n"] == 2


def test_un_500_permanente_hace_exactamente_tres_intentos_y_es_retryable():
    """``retries = 2`` significa 3 intentos: el original + 2. Ni 2 ni 4."""
    intentos = {"n": 0}

    def handler(request):
        intentos["n"] += 1
        return httpx.Response(500, json={"detail": "boom"})

    with pytest.raises(EfcClientError) as exc:
        _client(handler).resolve_organizacion("temex")

    assert intentos["n"] == 3
    assert exc.value.retryable is True


def test_un_timeout_permanente_hace_tres_intentos_y_es_retryable():
    intentos = {"n": 0}

    def handler(request):
        intentos["n"] += 1
        raise httpx.ConnectTimeout("se acabó el tiempo", request=request)

    with pytest.raises(EfcClientError) as exc:
        _client(handler).resolve_organizacion("temex")

    assert intentos["n"] == 3
    assert exc.value.retryable is True


def test_un_400_no_se_reintenta_y_extrae_el_code_del_cuerpo():
    """El corte en 4xx es lo que evita machacar a EFC con una petición que nunca va a pasar.

    Y el ``code`` extraído es lo que permite al worker ramificar **por campo**, nunca parseando el
    texto del mensaje: un texto cambia con cualquier refactor del otro repo.
    """
    intentos = {"n": 0}

    def handler(request):
        intentos["n"] += 1
        return httpx.Response(
            400,
            json={"error": {"code": "espacio_insuficiente", "message": "La licencia no tiene espacio"}},
        )

    with pytest.raises(EfcClientError) as exc:
        _client(handler).resolve_organizacion("temex")

    assert intentos["n"] == 1
    assert exc.value.status_code == 400
    assert exc.value.code == "espacio_insuficiente"
    assert exc.value.retryable is False
    assert "La licencia no tiene espacio" in str(exc.value)


@pytest.mark.parametrize("status_code", [401, 403])
def test_401_y_403_no_se_reintentan(status_code):
    """Una key mal configurada no mejora insistiendo: reintentarla solo gasta cuota y llena logs."""
    intentos = {"n": 0}

    def handler(request):
        intentos["n"] += 1
        return httpx.Response(status_code)

    with pytest.raises(EfcClientError) as exc:
        _client(handler).resolve_organizacion("temex")

    assert intentos["n"] == 1
    assert exc.value.retryable is False


def test_un_cuerpo_de_error_que_no_es_json_no_revienta():
    def handler(request):
        return httpx.Response(400, text="<html>502 Bad Gateway</html>")

    with pytest.raises(EfcClientError) as exc:
        _client(handler).resolve_organizacion("temex")

    assert exc.value.code is None
    assert exc.value.status_code == 400


def test_toda_llamada_manda_el_header_x_api_key():
    visto = {}

    def handler(request):
        visto["key"] = request.headers.get("X-Api-Key")
        return httpx.Response(200, json={"id": "org-1"})

    _client(handler).resolve_organizacion("temex")
    assert visto["key"] == KEY


def test_sin_url_configurada_no_toca_la_red_y_el_error_no_es_retryable():
    """``is_configured is False`` es lo que hace que todo el carril sea best-effort.

    Si esto tocara la red, cada operación del CRM con EFC apagado pagaría un timeout.
    """
    llamado = {"n": 0}

    def handler(request):
        llamado["n"] += 1
        return httpx.Response(200, json={})

    client = _client(handler, base_url="")
    assert client.is_configured is False

    with pytest.raises(EfcClientError) as exc:
        client.resolve_organizacion("temex")

    assert llamado["n"] == 0
    assert exc.value.retryable is False


def test_sin_api_key_tampoco_esta_configurado():
    def handler(request):
        return httpx.Response(200, json={})

    assert _client(handler, api_key="").is_configured is False


def test_la_base_url_con_y_sin_barra_final_dan_la_misma_url():
    urls = []

    def handler(request):
        urls.append(str(request.url))
        return httpx.Response(200, json={"id": "org-1"})

    _client(handler, base_url=BASE).resolve_organizacion("temex")
    _client(handler, base_url=BASE + "/").resolve_organizacion("temex")

    assert urls[0] == urls[1]
    assert "//organization" not in urls[0]


def test_la_subida_va_multipart_y_lleva_el_crm_document_ref():
    """El ref es la tercera capa de idempotencia: EFC devuelve 200 con el que ya existía."""
    visto = {}

    def handler(request):
        visto["content_type"] = request.headers.get("Content-Type", "")
        visto["body"] = request.content
        return httpx.Response(201, json={"id": "doc-1"})

    resp = _client(handler).upload_documento(
        "org-1", 1, 42, "MBL", "guia.pdf", b"%PDF-1.4 contenido", "application/pdf",
        crm_document_ref="SHPDOC-1-4471",
    )

    assert resp == {"id": "doc-1"}
    assert visto["content_type"].startswith("multipart/form-data")
    assert b"SHPDOC-1-4471" in visto["body"]
    assert b"%PDF-1.4 contenido" in visto["body"]
    # Nada de base64: el archivo viaja crudo dentro del multipart.
    assert b"base64" not in visto["body"]


def test_la_subida_usa_el_timeout_largo_y_los_metadatos_el_corto():
    """Los 8 s de los metadatos no alcanzan para un archivo de 25 MB, y un timeout de subida
    demasiado largo haría que el CRM vea un 504 opaco de nginx sin saber si el documento entró."""
    client = _client(handler=lambda r: httpx.Response(200, json={}), timeout_ms=8000, upload_timeout_ms=55000)
    assert client.timeout_s == 8.0
    assert client.upload_timeout_s == 55.0
    assert client.upload_timeout_s > client.timeout_s


def test_ensure_then_upload_puede_ramificar_por_el_code_del_404():
    """El 404 del expediente tiene que llegar al worker con su ``code`` y su ``status_code``.

    Es lo que dispara el ensure-then-upload; sin el code, el worker tendría que adivinar de qué es
    el 404 y crearía provisionales por cualquier ausencia.
    """
    def handler(request):
        return httpx.Response(
            404, json={"error": {"code": "expediente_no_encontrado", "message": "no está"}}
        )

    with pytest.raises(EfcClientError) as exc:
        _client(handler).upload_documento("org-1", 1, 42, "MBL", "g.pdf", b"x")

    assert exc.value.status_code == 404
    assert exc.value.code == "expediente_no_encontrado"
    assert exc.value.retryable is False


def test_la_descarga_devuelve_contenido_y_nombre_del_content_disposition():
    def handler(request):
        return httpx.Response(
            200,
            content=b"contenido binario",
            headers={"Content-Disposition": 'attachment; filename="factura.pdf"'},
        )

    contenido, nombre = _client(handler).download_documento("org-1", "doc-1")
    assert contenido == b"contenido binario"
    assert nombre == "factura.pdf"


def test_la_descarga_sin_content_disposition_cae_al_id_del_documento():
    def handler(request):
        return httpx.Response(200, content=b"x")

    _contenido, nombre = _client(handler).download_documento("org-1", "doc-9")
    assert nombre == "doc-9"
