"""Tests for Glances integration."""

from pathlib import Path
from unittest.mock import AsyncMock, patch

import httpx
import pytest

from compose_farm.config import Config, Host
from compose_farm.glances import (
    DEFAULT_GLANCES_PORT,
    ContainerStats,
    HostStats,
    _get_glances_address,
    fetch_all_container_stats,
    fetch_all_host_stats,
    fetch_container_stats,
    fetch_host_stats,
)


class TestHostStats:
    """Tests for HostStats dataclass."""

    def test_host_stats_creation(self) -> None:
        stats = HostStats(
            host="nas",
            cpu_percent=25.5,
            mem_percent=50.0,
            swap_percent=10.0,
            load=2.5,
            disk_percent=75.0,
        )
        assert stats.host == "nas"
        assert stats.cpu_percent == 25.5
        assert stats.mem_percent == 50.0
        assert stats.disk_percent == 75.0
        assert stats.error is None

    def test_host_stats_from_error(self) -> None:
        stats = HostStats.from_error("nas", "Connection refused")
        assert stats.host == "nas"
        assert stats.cpu_percent == 0
        assert stats.mem_percent == 0
        assert stats.error == "Connection refused"


class TestFetchHostStats:
    """Tests for fetch_host_stats function."""

    @pytest.mark.asyncio
    async def test_fetch_host_stats_success(self) -> None:
        quicklook_response = httpx.Response(
            200,
            json={
                "cpu": 25.5,
                "mem": 50.0,
                "swap": 5.0,
                "load": 2.5,
            },
        )
        fs_response = httpx.Response(
            200,
            json=[
                {"mnt_point": "/", "percent": 65.0},
                {"mnt_point": "/mnt/data", "percent": 80.0},
            ],
        )

        async def mock_get(url: str) -> httpx.Response:
            if "quicklook" in url:
                return quicklook_response
            return fs_response

        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(side_effect=mock_get)

            stats = await fetch_host_stats("nas", "192.168.1.6")

        assert stats.host == "nas"
        assert stats.cpu_percent == 25.5
        assert stats.mem_percent == 50.0
        assert stats.swap_percent == 5.0
        assert stats.load == 2.5
        assert stats.disk_percent == 65.0  # Root filesystem
        assert stats.error is None

    @pytest.mark.asyncio
    async def test_fetch_host_stats_http_error(self) -> None:
        mock_response = httpx.Response(500)

        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(return_value=mock_response)

            stats = await fetch_host_stats("nas", "192.168.1.6")

        assert stats.host == "nas"
        assert stats.error == "HTTP 500"
        assert stats.cpu_percent == 0

    @pytest.mark.asyncio
    async def test_fetch_host_stats_timeout(self) -> None:
        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(side_effect=httpx.TimeoutException("timeout"))

            stats = await fetch_host_stats("nas", "192.168.1.6")

        assert stats.host == "nas"
        assert stats.error == "timeout"

    @pytest.mark.asyncio
    async def test_fetch_host_stats_connection_error(self) -> None:
        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(
                side_effect=httpx.ConnectError("Connection refused")
            )

            stats = await fetch_host_stats("nas", "192.168.1.6")

        assert stats.host == "nas"
        assert stats.error is not None
        assert "Connection refused" in stats.error


class TestFetchAllHostStats:
    """Tests for fetch_all_host_stats function."""

    @pytest.mark.asyncio
    async def test_fetch_all_host_stats(self) -> None:
        config = Config(
            compose_dir=Path("/opt/compose"),
            hosts={
                "nas": Host(address="192.168.1.6"),
                "nuc": Host(address="192.168.1.2"),
            },
            stacks={"test": "nas"},
        )

        quicklook_response = httpx.Response(
            200,
            json={
                "cpu": 25.5,
                "mem": 50.0,
                "swap": 5.0,
                "load": 2.5,
            },
        )
        fs_response = httpx.Response(
            200,
            json=[{"mnt_point": "/", "percent": 70.0}],
        )

        async def mock_get(url: str) -> httpx.Response:
            if "quicklook" in url:
                return quicklook_response
            return fs_response

        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(side_effect=mock_get)

            stats = await fetch_all_host_stats(config)

        assert "nas" in stats
        assert "nuc" in stats
        assert stats["nas"].cpu_percent == 25.5
        assert stats["nuc"].cpu_percent == 25.5
        assert stats["nas"].disk_percent == 70.0


class TestDefaultPort:
    """Tests for default Glances port constant."""

    def test_default_port(self) -> None:
        assert DEFAULT_GLANCES_PORT == 61208


class TestContainerStats:
    """Tests for ContainerStats dataclass."""

    def test_container_stats_creation(self) -> None:
        stats = ContainerStats(
            name="nginx",
            host="nas",
            status="running",
            image="nginx:latest",
            cpu_percent=5.5,
            memory_usage=104857600,  # 100MB
            memory_limit=1073741824,  # 1GB
            memory_percent=9.77,
            network_rx=1000000,
            network_tx=500000,
            uptime="2 hours",
            ports="80->80/tcp",
            engine="docker",
        )
        assert stats.name == "nginx"
        assert stats.host == "nas"
        assert stats.cpu_percent == 5.5


class TestFetchContainerStats:
    """Tests for fetch_container_stats function."""

    @pytest.mark.asyncio
    async def test_fetch_container_stats_success(self) -> None:
        mock_response = httpx.Response(
            200,
            json=[
                {
                    "name": "nginx",
                    "status": "running",
                    "image": ["nginx:latest"],
                    "cpu_percent": 5.5,
                    "memory_usage": 104857600,
                    "memory_limit": 1073741824,
                    "network": {"cumulative_rx": 1000, "cumulative_tx": 500},
                    "uptime": "2 hours",
                    "ports": "80->80/tcp",
                    "engine": "docker",
                },
                {
                    "name": "redis",
                    "status": "running",
                    "image": ["redis:7"],
                    "cpu_percent": 1.2,
                    "memory_usage": 52428800,
                    "memory_limit": 1073741824,
                    "network": {},
                    "uptime": "3 hours",
                    "ports": "",
                    "engine": "docker",
                },
            ],
        )

        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(return_value=mock_response)

            containers, error = await fetch_container_stats("nas", "192.168.1.6")

        assert error is None
        assert containers is not None
        assert len(containers) == 2
        assert containers[0].name == "nginx"
        assert containers[0].host == "nas"
        assert containers[0].cpu_percent == 5.5
        assert containers[1].name == "redis"

    @pytest.mark.asyncio
    async def test_fetch_container_stats_empty_on_error(self) -> None:
        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(side_effect=httpx.TimeoutException("timeout"))

            containers, error = await fetch_container_stats("nas", "192.168.1.6")

        assert containers is None
        assert error == "Connection timed out"

    @pytest.mark.asyncio
    async def test_fetch_container_stats_handles_string_image(self) -> None:
        """Test that image field works as string (not just list)."""
        mock_response = httpx.Response(
            200,
            json=[
                {
                    "name": "test",
                    "status": "running",
                    "image": "myimage:v1",  # String instead of list
                    "cpu_percent": 0,
                    "memory_usage": 0,
                    "memory_limit": 1,
                    "network": {},
                    "uptime": "",
                    "ports": "",
                    "engine": "docker",
                },
            ],
        )

        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(return_value=mock_response)

            containers, error = await fetch_container_stats("nas", "192.168.1.6")

        assert error is None
        assert containers is not None
        assert len(containers) == 1
        assert containers[0].image == "myimage:v1"


class TestFetchAllContainerStats:
    """Tests for fetch_all_container_stats function."""

    @pytest.mark.asyncio
    async def test_fetch_all_container_stats(self) -> None:
        config = Config(
            compose_dir=Path("/opt/compose"),
            hosts={
                "nas": Host(address="192.168.1.6"),
                "nuc": Host(address="192.168.1.2"),
            },
            stacks={"test": "nas"},
        )

        mock_response = httpx.Response(
            200,
            json=[
                {
                    "name": "nginx",
                    "status": "running",
                    "image": ["nginx:latest"],
                    "cpu_percent": 5.5,
                    "memory_usage": 104857600,
                    "memory_limit": 1073741824,
                    "network": {},
                    "uptime": "2 hours",
                    "ports": "",
                    "engine": "docker",
                },
            ],
        )

        with patch("httpx.AsyncClient") as mock_client:
            mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
            mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value.get = AsyncMock(return_value=mock_response)

            containers = await fetch_all_container_stats(config)

        # 2 hosts x 1 container each = 2 containers
        assert len(containers) == 2
        hosts = {c.host for c in containers}
        assert "nas" in hosts
        assert "nuc" in hosts


class TestGetGlancesAddress:
    """Tests for _get_glances_address function."""

    def test_returns_host_address_outside_container(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Without CF_WEB_STACK, always return host address."""
        monkeypatch.delenv("CF_WEB_STACK", raising=False)
        host = Host(address="192.168.1.6")
        result = _get_glances_address("nas", host, "glances")
        assert result == "192.168.1.6"

    def test_returns_host_address_without_glances_container(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """In container without glances_stack config, return host address."""
        monkeypatch.setenv("CF_WEB_STACK", "compose-farm")
        host = Host(address="192.168.1.6")
        result = _get_glances_address("nas", host, None)
        assert result == "192.168.1.6"

    def test_returns_container_name_for_web_stack_host(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Local host uses container name in container mode."""
        monkeypatch.setenv("CF_WEB_STACK", "compose-farm")
        host = Host(address="192.168.1.6")
        result = _get_glances_address("nas", host, "glances", local_host="nas")
        assert result == "glances"

    def test_returns_host_address_for_non_local_host(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Non-local hosts use their IP address even in container mode."""
        monkeypatch.setenv("CF_WEB_STACK", "compose-farm")
        host = Host(address="192.168.1.2")
        result = _get_glances_address("nuc", host, "glances", local_host="nas")
        assert result == "192.168.1.2"

    def test_fallback_to_is_local_detection(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Without explicit local host, falls back to is_local detection."""
        monkeypatch.setenv("CF_WEB_STACK", "compose-farm")
        # Use localhost which should be detected as local
        host = Host(address="localhost")
        result = _get_glances_address("local", host, "glances")
        assert result == "glances"

    def test_remote_host_not_affected_by_container_mode(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Remote hosts always use their IP, even in container mode."""
        monkeypatch.setenv("CF_WEB_STACK", "compose-farm")
        host = Host(address="192.168.1.100")
        result = _get_glances_address("remote", host, "glances")
        assert result == "192.168.1.100"
