"""
Tests E2E del Agent CLI contra un servidor Jama real.

Requiere variables de entorno:
  JAMA_TEST_SERVER  — URL del servidor (ej: https://api.jamapt.ideasareal.ai)
  JAMA_TEST_EMAIL   — Email del usuario
  JAMA_TEST_PASSWORD — Contraseña del usuario

Si no están definidas, los tests se saltan automáticamente.

Uso:
  JAMA_TEST_SERVER=https://api.jamapt.ideasareal.ai \
  JAMA_TEST_EMAIL=admin@jama.security \
  JAMA_TEST_PASSWORD=JamaAdmin2026! \
  python3 -m pytest tests/test_e2e_live.py -v
"""
import asyncio
import json
import os
import shutil
from pathlib import Path
from unittest.mock import patch

import httpx
import pytest

# Configuración desde env vars
TEST_SERVER = os.environ.get("JAMA_TEST_SERVER", "")
TEST_EMAIL = os.environ.get("JAMA_TEST_EMAIL", "")
TEST_PASSWORD = os.environ.get("JAMA_TEST_PASSWORD", "")

SKIP_REASON = "Variables JAMA_TEST_SERVER/EMAIL/PASSWORD no definidas"
live_test = pytest.mark.skipif(
    not (TEST_SERVER and TEST_EMAIL and TEST_PASSWORD),
    reason=SKIP_REASON,
)


@live_test
class TestHealthEndpoint:
    """Verifica que el servidor está respondiendo."""

    def test_health_check(self):
        """GET /api/health retorna 200."""
        resp = httpx.get(f"{TEST_SERVER}/api/health", timeout=10)
        assert resp.status_code == 200
        data = resp.json()
        assert data.get("status") in ("ok", "healthy")

    def test_api_docs_available(self):
        """Swagger docs accesibles."""
        resp = httpx.get(f"{TEST_SERVER}/api/docs", timeout=10, follow_redirects=True)
        assert resp.status_code == 200


@live_test
class TestLoginFlow:
    """Tests del flujo de autenticación."""

    def test_login_success(self):
        """Login con credenciales válidas retorna tokens."""
        resp = httpx.post(
            f"{TEST_SERVER}/api/v1/auth/login",
            json={"email": TEST_EMAIL, "password": TEST_PASSWORD},
            timeout=15,
        )
        assert resp.status_code == 200
        data = resp.json()
        assert "access_token" in data
        assert "refresh_token" in data
        assert data.get("token_type") == "bearer"
        assert "user" in data

    def test_login_wrong_password(self):
        """Login con contraseña incorrecta retorna 401."""
        resp = httpx.post(
            f"{TEST_SERVER}/api/v1/auth/login",
            json={"email": TEST_EMAIL, "password": "wrong-password"},
            timeout=15,
        )
        assert resp.status_code == 401

    def test_login_nonexistent_email(self):
        """Login con email inexistente retorna 401."""
        resp = httpx.post(
            f"{TEST_SERVER}/api/v1/auth/login",
            json={"email": "noexiste@test.com", "password": "x"},
            timeout=15,
        )
        assert resp.status_code == 401


@live_test
class TestAgentRegistration:
    """Tests del registro de agents."""

    @pytest.fixture
    def auth_token(self):
        """Obtiene un JWT válido."""
        resp = httpx.post(
            f"{TEST_SERVER}/api/v1/auth/login",
            json={"email": TEST_EMAIL, "password": TEST_PASSWORD},
            timeout=15,
        )
        return resp.json()["access_token"]

    def test_register_agent(self, auth_token):
        """Registrar agent retorna UUID válido."""
        resp = httpx.post(
            f"{TEST_SERVER}/api/v1/agents/register",
            json={
                "name": "test-e2e-agent",
                "os_type": "linux",
                "capabilities": {"tools": []},
            },
            headers={"Authorization": f"Bearer {auth_token}"},
            timeout=15,
        )
        assert resp.status_code == 200
        data = resp.json()
        assert "id" in data
        assert len(data["id"]) == 36  # UUID
        assert data["name"] == "test-e2e-agent"
        assert data["status"] == "offline"

        # Cleanup: eliminar el agent
        httpx.delete(
            f"{TEST_SERVER}/api/v1/agents/{data['id']}",
            headers={"Authorization": f"Bearer {auth_token}"},
            timeout=10,
        )

    def test_register_without_auth(self):
        """Registrar agent sin auth retorna 401."""
        resp = httpx.post(
            f"{TEST_SERVER}/api/v1/agents/register",
            json={"name": "unauth-agent", "os_type": "linux", "capabilities": {}},
            timeout=15,
        )
        assert resp.status_code in (401, 403)

    def test_list_agents(self, auth_token):
        """Listar agents retorna array."""
        resp = httpx.get(
            f"{TEST_SERVER}/api/v1/agents/",
            headers={"Authorization": f"Bearer {auth_token}"},
            timeout=15,
        )
        assert resp.status_code == 200
        data = resp.json()
        assert "agents" in data
        assert isinstance(data["agents"], list)
        assert "total" in data


@live_test
class TestCLILoginCommand:
    """Tests del comando login del CLI."""

    @pytest.fixture(autouse=True)
    def clean_credentials(self):
        """Limpia credenciales antes y después de cada test."""
        creds_path = Path.home() / ".jama" / "credentials.json"
        backup = None
        if creds_path.exists():
            backup = creds_path.read_text()
        yield
        # Restaurar
        if backup:
            creds_path.write_text(backup)
        elif creds_path.exists():
            creds_path.unlink()

    def test_cli_login(self):
        """El módulo auth.login() guarda credenciales correctamente."""
        from jama_agent.auth import login, load_credentials, clear_credentials

        creds = login(TEST_SERVER, TEST_EMAIL, TEST_PASSWORD)
        assert creds["server"] == TEST_SERVER.rstrip("/")
        assert creds["email"] == TEST_EMAIL
        assert creds["access_token"]
        assert creds["refresh_token"]

        # Verificar que se guardaron en disco
        stored = load_credentials()
        assert stored is not None
        assert stored["email"] == TEST_EMAIL
        assert stored["access_token"] == creds["access_token"]

        clear_credentials()

    def test_cli_register_agent(self):
        """register_agent() retorna UUID y actualiza credenciales."""
        from jama_agent.auth import login, register_agent, load_credentials

        creds = login(TEST_SERVER, TEST_EMAIL, TEST_PASSWORD)
        agent_id = register_agent(TEST_SERVER, creds["access_token"])

        assert agent_id
        assert len(agent_id) == 36  # UUID

        # Verificar que se guardó en credenciales
        stored = load_credentials()
        assert stored["agent_id"] == agent_id

        # Cleanup
        httpx.delete(
            f"{TEST_SERVER}/api/v1/agents/{agent_id}",
            headers={"Authorization": f"Bearer {creds['access_token']}"},
            timeout=10,
        )


@live_test
class TestCLIStatusCommand:
    """Tests del comando status del CLI."""

    @pytest.fixture(autouse=True)
    def setup_auth(self):
        """Login antes de tests de status."""
        from jama_agent.auth import login, clear_credentials

        self.creds = login(TEST_SERVER, TEST_EMAIL, TEST_PASSWORD)
        yield
        clear_credentials()

    def test_status_shows_credentials(self):
        """load_credentials retorna datos guardados tras login."""
        from jama_agent.auth import load_credentials

        stored = load_credentials()
        assert stored is not None
        assert stored["server"] == TEST_SERVER.rstrip("/")
        assert stored["email"] == TEST_EMAIL


@live_test
class TestWebSocketConnect:
    """Tests de la conexión WebSocket."""

    @pytest.fixture
    def auth_and_agent(self):
        """Login + register agent, cleanup al final."""
        from jama_agent.auth import login, register_agent

        creds = login(TEST_SERVER, TEST_EMAIL, TEST_PASSWORD)
        agent_id = register_agent(TEST_SERVER, creds["access_token"])
        yield {
            "server": TEST_SERVER,
            "agent_id": agent_id,
            "token": creds["access_token"],
        }
        # Cleanup
        httpx.delete(
            f"{TEST_SERVER}/api/v1/agents/{agent_id}",
            headers={"Authorization": f"Bearer {creds['access_token']}"},
            timeout=10,
        )

    @pytest.mark.asyncio
    async def test_ws_connect_and_heartbeat(self, auth_and_agent):
        """Agent puede conectar por WebSocket y enviar heartbeat."""
        from jama_agent.connection import AgentConnection

        conn = AgentConnection(
            server=auth_and_agent["server"],
            agent_id=auth_and_agent["agent_id"],
            token=auth_and_agent["token"],
            heartbeat_interval=5,
            reconnect_max_delay=10,
        )

        connect_task = asyncio.create_task(conn.connect())

        # Esperar hasta 10s a que conecte
        for _ in range(100):
            if conn.is_connected:
                break
            await asyncio.sleep(0.1)

        connected = conn.is_connected

        await conn.disconnect()
        connect_task.cancel()
        try:
            await connect_task
        except asyncio.CancelledError:
            pass

        assert connected, "No se pudo conectar al WebSocket del servidor"

    @pytest.mark.asyncio
    async def test_ws_rejects_invalid_agent_id(self):
        """WebSocket rechaza agent_id no UUID."""
        from jama_agent.connection import AgentConnection

        conn = AgentConnection(
            server=TEST_SERVER,
            agent_id="not-a-uuid",
            token="fake-token",
        )

        connect_task = asyncio.create_task(conn.connect())
        await asyncio.sleep(2)

        # No debería conectar
        assert not conn.is_connected

        conn._running = False
        connect_task.cancel()
        try:
            await connect_task
        except asyncio.CancelledError:
            pass


@live_test
class TestDiscoverCommand:
    """Tests del comando discover (no requiere servidor)."""

    def test_discover_returns_dict(self):
        """discover_network retorna estructura correcta."""
        from jama_agent.discovery import discover_network

        result = discover_network()
        assert isinstance(result, dict)
        assert "hosts" in result
        assert "subnet" in result
        assert "method" in result
        assert "total" in result
        assert isinstance(result["hosts"], list)

    def test_discover_with_subnet(self):
        """discover_network con subnet explícita."""
        from jama_agent.discovery import discover_network

        result = discover_network(subnet="192.168.1")
        assert result["subnet"] == "192.168.1"


@live_test
class TestDisconnectCommand:
    """Tests del comando disconnect (limpieza de credenciales)."""

    def test_clear_credentials(self):
        """clear_credentials elimina el archivo."""
        from jama_agent.auth import login, clear_credentials, load_credentials

        login(TEST_SERVER, TEST_EMAIL, TEST_PASSWORD)
        assert load_credentials() is not None

        clear_credentials()
        assert load_credentials() is None


@live_test
class TestTokenRefresh:
    """Tests del refresh de tokens."""

    def test_refresh_token(self):
        """refresh_token genera nuevo access_token válido."""
        import time
        from jama_agent.auth import login, refresh_token as do_refresh

        creds = login(TEST_SERVER, TEST_EMAIL, TEST_PASSWORD)

        # Esperar 1s para que iat cambie y el token sea diferente
        time.sleep(1.1)

        new_token = do_refresh(TEST_SERVER, creds["refresh_token"])
        assert new_token
        assert len(new_token) > 50  # Token válido (JWT)
