# -*- coding: utf-8 -*-
import datetime
import re
import pytest
import requests
from nati_log.client import NatiLogClient

@pytest.fixture
def api_urls():
    return {
        "api": "http://fake-api/api",
        "login": "http://fake-api/api/auth/usuarios/login/",
        "app_slash": "http://fake-api/api/aplicaciones/5/",
        "app": "http://fake-api/api/aplicaciones/5",
        "evento": "http://fake-api/api/evento/",
    }

@pytest.fixture
def mock_login_ok(requests_mock, api_urls):
    def _make(token="token-1"):
        requests_mock.post(api_urls["login"], json={"access": token}, status_code=200)
        return token
    return _make

@pytest.fixture
def mock_login_seq(requests_mock, api_urls):
    def _make(seq):
        # seq = list of dicts: {"json": {...}, "status_code": ...}
        requests_mock.post(api_urls["login"], seq)
    return _make

@pytest.fixture
def mock_app_estado(requests_mock, api_urls):
    def _make(estado=True, status_code=200, sequence=None, exc=None):
        if exc:
            requests_mock.get(api_urls["app_slash"], exc=exc)
            requests_mock.get(api_urls["app"], exc=exc)
        elif sequence:
            # sequence list of responses for trailing slash first, then no slash
            requests_mock.get(api_urls["app_slash"], sequence)
            requests_mock.get(api_urls["app"], sequence)
        else:
            payload = {"estado": estado}
            requests_mock.get(api_urls["app_slash"], json=payload, status_code=status_code)
            requests_mock.get(api_urls["app"], json=payload, status_code=status_code)
    return _make

def build_client(api_urls):
    return NatiLogClient(
        api_url=api_urls["api"],
        api_url_login=api_urls["login"],
        app_id=5,
        username="user",
        password="pass",
    )

def _last_event_request(requests_mock, api_urls):
    # Return last POST to evento if exists
    posts = [r for r in requests_mock.request_history if r.method == "POST" and r.url == api_urls["evento"]]
    return posts[-1] if posts else None

def test_registrar_evento_success(requests_mock, api_urls, mock_login_ok, mock_app_estado):
    mock_login_ok("token-1")
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], json={"status": "ok"}, status_code=201)
    c = build_client(api_urls)
    resp = c.info("Evento exitoso", datos={"k": "v"})
    assert resp == {"status": "ok"}
    last = _last_event_request(requests_mock, api_urls)
    assert last is not None
    data = last.json()
    assert data["tipo_evento"] == "INFO"
    assert data["datos"]["k"] == "v"
    assert data["aplicacion"] == 5
    assert last.headers.get("Authorization") == "Bearer token-1"

def test_registrar_evento_app_inactiva_omite_envio(requests_mock, api_urls, mock_login_ok, mock_app_estado):
    mock_login_ok("token-x")
    mock_app_estado(False)
    requests_mock.post(api_urls["evento"], json={"status": "ok"}, status_code=201)
    c = build_client(api_urls)
    r = c.warning("No enviar")
    assert r == {"detail": "Aplicación inactiva. Evento omitido."}
    assert _last_event_request(requests_mock, api_urls) is None  # no POST

def test_registrar_evento_reintenta_en_401(requests_mock, api_urls, mock_login_seq, mock_app_estado):
    mock_login_seq([
        {"json": {"access": "token-1"}, "status_code": 200},
        {"json": {"access": "token-2"}, "status_code": 200},
    ])
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], [
        {"status_code": 401},
        {"json": {"status": "ok"}, "status_code": 201},
    ])
    c = build_client(api_urls)
    r = c.error("Debe reintentar")
    assert r == {"status": "ok"}
    posts = [p for p in requests_mock.request_history if p.method == "POST" and p.url == api_urls["evento"]]
    assert len(posts) == 2
    # Second request should carry new token
    assert posts[-1].headers.get("Authorization") == "Bearer token-2"

def test_registrar_evento_401_refresh_falla_raise(requests_mock, api_urls, mock_login_seq, mock_app_estado):
    # First login ok => token exists. Event 401 triggers refresh; second login fails => raise original 401.
    mock_login_seq([
        {"json": {"access": "token-1"}, "status_code": 200},
        {"status_code": 401},  # refresh fails
    ])
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], status_code=401)
    c = build_client(api_urls)
    with pytest.raises(requests.exceptions.HTTPError):
        c.info("Debe fallar")
    posts = [p for p in requests_mock.request_history if p.method == "POST" and p.url == api_urls["evento"]]
    assert len(posts) == 1  # no second attempt due to refresh failure

def test_registrar_evento_con_fecha_personalizada(requests_mock, api_urls, mock_login_ok, mock_app_estado):
    mock_login_ok()
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], json={"status": "ok"}, status_code=201)
    c = build_client(api_urls)
    fecha = datetime.datetime(2024, 1, 1, 12, 0, 0).isoformat()
    c.debug("Con fecha", fecha=fecha)
    assert _last_event_request(requests_mock, api_urls).json()["fecha"] == fecha
    assert _last_event_request(requests_mock, api_urls).json()["tipo_evento"] == "DEBUG"

def test_registrar_evento_fecha_auto_formato(requests_mock, api_urls, mock_login_ok, mock_app_estado):
    mock_login_ok()
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], json={"status": "ok"}, status_code=201)
    c = build_client(api_urls)
    c.critical("Auto fecha")
    fecha = _last_event_request(requests_mock, api_urls).json()["fecha"]
    assert re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:", fecha)

def test_registrar_evento_sin_datos_envia_dict_vacio(requests_mock, api_urls, mock_login_ok, mock_app_estado):
    mock_login_ok()
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], json={"status": "ok"}, status_code=201)
    c = build_client(api_urls)
    c.error("Sin datos")
    body = _last_event_request(requests_mock, api_urls).json()
    assert body["datos"] == {}

def test_registrar_evento_login_falla_omite(requests_mock, api_urls, mock_app_estado):
    requests_mock.post(api_urls["login"], status_code=401)
    mock_app_estado(True)  # estado no se consulta porque token None => app_estado False
    requests_mock.post(api_urls["evento"], json={"status": "ok"}, status_code=201)
    c = build_client(api_urls)
    r = c.info("No debe enviarse")
    assert r == {"detail": "Aplicación inactiva. Evento omitido."}
    assert _last_event_request(requests_mock, api_urls) is None

def test_registrar_evento_refresh_token_utilizado(requests_mock, api_urls, mock_login_seq, mock_app_estado):
    # Estado True; evento 401 -> refresh OK -> segundo POST 201
    mock_login_seq([
        {"json": {"access": "A"}, "status_code": 200},
        {"json": {"access": "B"}, "status_code": 200},
    ])
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], [
        {"status_code": 401},
        {"json": {"res": "ok"}, "status_code": 201},
    ])
    c = build_client(api_urls)
    r = c.warning("Reintento")
    assert r == {"res": "ok"}
    posts = [p for p in requests_mock.request_history if p.method == "POST" and p.url == api_urls["evento"]]
    assert len(posts) == 2
    assert posts[-1].headers.get("Authorization") == "Bearer B"

def test_registrar_evento_app_estado_401_en_init(requests_mock, api_urls, mock_login_seq):
    # During init: estado request 401 triggers token refresh, then second estado OK False
    mock_login_seq([
        {"json": {"access": "tok1"}, "status_code": 200},
        {"json": {"access": "tok2"}, "status_code": 200},
    ])
    # First call (with slash) 401, second (without slash) returns estado False
    requests_mock.get(api_urls["app_slash"], status_code=401)
    requests_mock.get(api_urls["app"], json={"estado": False}, status_code=200)
    c = build_client(api_urls)
    assert c.token == "tok2"
    assert c.app_estado is False
    # Event omitted due to inactive
    requests_mock.post(api_urls["evento"], json={"ok": True}, status_code=201)
    r = c.debug("No enviar")
    assert r == {"detail": "Aplicación inactiva. Evento omitido."}

def test_registrar_evento_token_expira_y_se_refresca(requests_mock, api_urls, mock_login_seq, mock_app_estado):
    mock_login_seq([
        {"json": {"access": "t1"}, "status_code": 200},
        {"json": {"access": "t2"}, "status_code": 200},
    ])
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], [
        {"status_code": 401},
        {"json": {"final": True}, "status_code": 201},
    ])
    c = build_client(api_urls)
    r = c.debug("Refresh")
    assert r == {"final": True}
    posts = [p for p in requests_mock.request_history if p.method == "POST" and p.url == api_urls["evento"]]
    assert posts[0].headers.get("Authorization") == "Bearer t1"
    assert posts[1].headers.get("Authorization") == "Bearer t2"

def test_registrar_evento_sin_token_por_timeout(requests_mock, api_urls, mock_app_estado):
    requests_mock.post(api_urls["login"], exc=requests.exceptions.ConnectTimeout)
    mock_app_estado(True)
    requests_mock.post(api_urls["evento"], json={"status": "ok"}, status_code=201)
    c = build_client(api_urls)
    assert c.token is None
    # app_estado False (por implementación actual)
    assert c.app_estado is False
    r = c.error("Omitido")
    assert r == {"detail": "Aplicación inactiva. Evento omitido."}
    assert _last_event_request(requests_mock, api_urls) is None
