"""
Backend tests for Portero GPS module.
Tests auth (login/me), device retrieval, command send, log, buttons, admin endpoints,
and auth guards.
"""
import os
import pytest
import requests

BASE_URL = os.environ.get("REACT_APP_BACKEND_URL", "https://doc-manager-111.preview.emergentagent.com").rstrip("/")
API = f"{BASE_URL}/api/portero"

EMAIL = "ptn@4sat.cl"
PASSWORD = "Ptn@2026"
DEVICE_ID = 910069
IMEI = "866381051877667"


@pytest.fixture(scope="module")
def token_and_user():
    """Login to portero and return (token, user)."""
    resp = requests.post(f"{API}/auth/login", json={"email": EMAIL, "password": PASSWORD}, timeout=30)
    if resp.status_code != 200:
        pytest.skip(f"Portero login failed: {resp.status_code} {resp.text}")
    data = resp.json()
    return data["access_token"], data["user"]


@pytest.fixture
def auth_headers(token_and_user):
    token, _ = token_and_user
    return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}


# ===== AUTH =====

class TestAuth:
    def test_login_success(self):
        r = requests.post(f"{API}/auth/login", json={"email": EMAIL, "password": PASSWORD}, timeout=30)
        assert r.status_code == 200, r.text
        data = r.json()
        assert "access_token" in data and isinstance(data["access_token"], str) and len(data["access_token"]) > 20
        user = data["user"]
        assert user["email"] == EMAIL.lower()
        assert "id" in user and isinstance(user["id"], str)
        assert "nombre" in user
        assert "rol" in user

    def test_login_bad_credentials(self):
        r = requests.post(f"{API}/auth/login", json={"email": EMAIL, "password": "WRONG_PASSWORD_xxx"}, timeout=30)
        assert r.status_code == 401

    def test_login_missing_fields(self):
        r = requests.post(f"{API}/auth/login", json={"email": ""}, timeout=30)
        assert r.status_code in (400, 422)

    def test_me_endpoint(self, auth_headers, token_and_user):
        _, user = token_and_user
        r = requests.get(f"{API}/auth/me", headers=auth_headers, timeout=30)
        assert r.status_code == 200
        d = r.json()
        assert d["email"] == user["email"]
        assert d["id"] == user["id"]
        assert "rol" in d and "nombre" in d


# ===== AUTH GUARD =====

class TestAuthGuard:
    @pytest.mark.parametrize("method,path", [
        ("get", "/auth/me"),
        ("get", "/dispositivos"),
        ("post", "/comando"),
        ("get", "/log"),
        ("get", "/botones"),
        ("get", "/admin/usuarios"),
    ])
    def test_no_token_returns_401(self, method, path):
        fn = getattr(requests, method)
        kwargs = {"timeout": 15}
        if method == "post":
            kwargs["json"] = {}
        r = fn(f"{API}{path}", **kwargs)
        assert r.status_code == 401, f"{method.upper()} {path} expected 401 got {r.status_code}"

    def test_invalid_token_returns_401(self):
        r = requests.get(f"{API}/auth/me", headers={"Authorization": "Bearer invalidtoken.abc.xyz"}, timeout=15)
        assert r.status_code == 401


# ===== DEVICES =====

class TestDispositivos:
    def test_get_devices(self, auth_headers):
        r = requests.get(f"{API}/dispositivos", headers=auth_headers, timeout=45)
        assert r.status_code == 200, r.text
        data = r.json()
        assert "dispositivos" in data
        assert isinstance(data["dispositivos"], list)
        # Should contain PTN device
        ptn = next((d for d in data["dispositivos"] if d.get("id") == DEVICE_ID), None)
        assert ptn is not None, f"Device {DEVICE_ID} not found in list"
        assert ptn.get("imei") == IMEI


# ===== BUTTONS =====

class TestBotones:
    def test_get_my_buttons(self, auth_headers):
        r = requests.get(f"{API}/botones", headers=auth_headers, timeout=30)
        assert r.status_code == 200
        data = r.json()
        assert "botones" in data
        # Expect at least one config for device 910069 with 3 buttons
        configs = data["botones"]
        assert isinstance(configs, list) and len(configs) >= 1
        cfg = next((c for c in configs if c.get("device_id") == DEVICE_ID), None)
        assert cfg is not None, "No config for device 910069"
        assert isinstance(cfg.get("botones"), list)
        assert len(cfg["botones"]) == 3, f"Expected 3 buttons, got {len(cfg['botones'])}"
        # Verify button names
        nombres = [b.get("nombre") for b in cfg["botones"]]
        for expected in ("Abrir Portón", "Luz Patio", "Timbre"):
            assert expected in nombres, f"Button '{expected}' missing. Got: {nombres}"


# ===== COMMAND =====

class TestComando:
    def test_send_command_getinfo(self, auth_headers):
        payload = {"device_id": DEVICE_ID, "command": "getinfo", "boton_nombre": "test-getinfo"}
        r = requests.post(f"{API}/comando", headers=auth_headers, json=payload, timeout=30)
        assert r.status_code == 200, r.text
        data = r.json()
        # success can be false if device offline, but response structure should be valid
        assert "success" in data
        assert "errors" in data or "raw" in data
        # If offline, errors should mention 'Sin conexión'
        errs = data.get("errors") or []
        if errs:
            joined = " ".join(str(e) for e in errs)
            # Accept either connection error or success
            assert "Sin conexión GPRS" in joined or data.get("success") is True

    def test_send_command_missing_fields(self, auth_headers):
        r = requests.post(f"{API}/comando", headers=auth_headers, json={}, timeout=15)
        assert r.status_code == 400


# ===== LOG =====

class TestLog:
    def test_get_log(self, auth_headers):
        r = requests.get(f"{API}/log", headers=auth_headers, timeout=30)
        assert r.status_code == 200
        data = r.json()
        assert "logs" in data
        assert isinstance(data["logs"], list)


# ===== ADMIN =====

class TestAdmin:
    def test_list_usuarios(self, auth_headers):
        r = requests.get(f"{API}/admin/usuarios", headers=auth_headers, timeout=30)
        assert r.status_code == 200, r.text
        data = r.json()
        assert "usuarios" in data
        assert isinstance(data["usuarios"], list)
        # Should include ptn user
        assert any(u.get("email") == EMAIL.lower() for u in data["usuarios"])

    def test_admin_get_config(self, auth_headers, token_and_user):
        _, user = token_and_user
        r = requests.get(f"{API}/admin/config/{user['id']}", headers=auth_headers, timeout=30)
        assert r.status_code == 200
        data = r.json()
        assert "configs" in data

    def test_admin_set_config_persists(self, auth_headers, token_and_user):
        """PUT admin config with 3 test buttons then verify persistence via GET."""
        _, user = token_and_user
        payload = {
            "device_name": "PTN - PORTON PRINCIPAL",
            "layout": "grid",
            "botones": [
                {"nombre": "Abrir Portón", "comando": "RELAY1,1#", "tipo": "pulso", "color": "#10b981", "icono": "door"},
                {"nombre": "Luz Patio", "comando": "RELAY2,1#", "comando_off": "RELAY2,0#", "tipo": "toggle", "color": "#3b82f6", "icono": "power"},
                {"nombre": "Timbre", "comando": "BUZZER,1#", "tipo": "pulso", "color": "#f59e0b", "icono": "bell"},
            ],
        }
        r = requests.put(f"{API}/admin/config/{user['id']}/{DEVICE_ID}", headers=auth_headers, json=payload, timeout=30)
        assert r.status_code == 200, r.text
        # Verify by fetching botones
        r2 = requests.get(f"{API}/botones", headers=auth_headers, timeout=30)
        assert r2.status_code == 200
        cfg = next((c for c in r2.json().get("botones", []) if c.get("device_id") == DEVICE_ID), None)
        assert cfg is not None
        assert cfg.get("device_name") == "PTN - PORTON PRINCIPAL"
        assert len(cfg["botones"]) == 3


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
