"""Tests for llmhosts.tunnel -- models, detector, manager."""

from __future__ import annotations

from unittest.mock import AsyncMock, patch

import pytest

from llmhosts.tunnel.detector import TunnelDetector
from llmhosts.tunnel.manager import TunnelManager
from llmhosts.tunnel.models import (
    TunnelConfig,
    TunnelProvider,
    TunnelStatus,
)
from llmhosts.tunnel.relay import RelayTunnel

# ---------------------------------------------------------------------------
# Models
# ---------------------------------------------------------------------------


class TestTunnelModels:
    def test_tunnel_status_defaults(self) -> None:
        s = TunnelStatus()
        assert s.provider == TunnelProvider.NONE
        assert s.active is False
        assert s.url is None

    def test_tunnel_config_defaults(self) -> None:
        cfg = TunnelConfig()
        assert cfg.port == 4000
        assert cfg.auth_required is True
        assert cfg.relay_url is None
        assert cfg.relay_token is None

    def test_tunnel_provider_values(self) -> None:
        assert TunnelProvider.RELAY.value == "relay"
        assert TunnelProvider.NONE.value == "none"

    def test_tunnel_config_relay_fields(self) -> None:
        cfg = TunnelConfig(relay_url="wss://llmhosts.com/api/relay/connect", relay_token="tok_abc123")
        assert cfg.relay_url == "wss://llmhosts.com/api/relay/connect"
        assert cfg.relay_token == "tok_abc123"

    def test_tunnel_status_with_relay_provider(self) -> None:
        s = TunnelStatus(provider=TunnelProvider.RELAY, active=True, url="wss://relay.llmhosts.com")
        assert s.provider == TunnelProvider.RELAY
        assert s.active is True
        assert s.display_url == "wss://relay.llmhosts.com"

    def test_tunnel_status_display_url_placeholder(self) -> None:
        s = TunnelStatus()
        assert s.display_url == "(not connected)"

    def test_tunnel_config_port_validation(self) -> None:
        cfg = TunnelConfig(port=8080)
        assert cfg.port == 8080

        with pytest.raises(Exception):  # noqa: B017
            TunnelConfig(port=0)

        with pytest.raises(Exception):  # noqa: B017
            TunnelConfig(port=70000)


# ---------------------------------------------------------------------------
# TunnelDetector
# ---------------------------------------------------------------------------


class TestTunnelDetector:
    @pytest.mark.asyncio
    async def test_detect_relay_available(self) -> None:
        with patch.object(TunnelDetector, "check_relay", return_value=True):
            providers = await TunnelDetector.detect()
            assert providers == [TunnelProvider.RELAY]

    @pytest.mark.asyncio
    async def test_detect_relay_not_available(self) -> None:
        with patch.object(TunnelDetector, "check_relay", return_value=False):
            providers = await TunnelDetector.detect()
            assert providers == []

    def test_check_relay_available(self) -> None:
        with patch("llmhosts.tunnel.detector.TunnelDetector.check_relay", return_value=True):
            assert TunnelDetector.check_relay() is True

    def test_check_relay_not_available(self) -> None:
        with patch("llmhosts.tunnel.relay.RELAY_AVAILABLE", False):
            assert TunnelDetector.check_relay() is False


# ---------------------------------------------------------------------------
# TunnelManager
# ---------------------------------------------------------------------------


class TestTunnelManager:
    def test_initial_state(self) -> None:
        mgr = TunnelManager()
        assert mgr.active_provider == TunnelProvider.NONE
        assert mgr.auth is None

    @pytest.mark.asyncio
    async def test_start_relay_not_available(self) -> None:
        with patch.object(TunnelDetector, "detect", return_value=[]):
            mgr = TunnelManager()
            status = await mgr.start()
            assert status.active is False
            assert "not available" in (status.error or "").lower()

    @pytest.mark.asyncio
    async def test_start_relay_success(self) -> None:
        fake_status = TunnelStatus(
            provider=TunnelProvider.RELAY,
            active=True,
            url="wss://llmhosts.com/api/relay/connect",
            local_port=4000,
        )
        with (
            patch.object(TunnelDetector, "detect", return_value=[TunnelProvider.RELAY]),
            patch.object(RelayTunnel, "start", return_value=fake_status),
        ):
            mgr = TunnelManager()
            config = TunnelConfig(auth_required=False, relay_token="tok_abc")
            status = await mgr.start(config)
            assert status.active is True
            assert mgr.active_provider == TunnelProvider.RELAY

    @pytest.mark.asyncio
    async def test_start_relay_fails(self) -> None:
        fail_status = TunnelStatus(
            provider=TunnelProvider.RELAY,
            active=False,
            local_port=4000,
            error="Connection refused",
        )
        with (
            patch.object(TunnelDetector, "detect", return_value=[TunnelProvider.RELAY]),
            patch.object(RelayTunnel, "start", return_value=fail_status),
        ):
            mgr = TunnelManager()
            config = TunnelConfig(auth_required=False, relay_token="tok_abc")
            status = await mgr.start(config)
            assert status.active is False
            assert mgr.active_provider == TunnelProvider.NONE

    @pytest.mark.asyncio
    async def test_stop(self) -> None:
        mgr = TunnelManager()
        mgr._relay = AsyncMock()
        mgr._active_provider = TunnelProvider.RELAY
        await mgr.stop()
        assert mgr.active_provider == TunnelProvider.NONE

    @pytest.mark.asyncio
    async def test_status_no_active(self) -> None:
        mgr = TunnelManager()
        status = await mgr.status()
        assert status.active is False
        assert status.provider == TunnelProvider.NONE

    @pytest.mark.asyncio
    async def test_status_relay_active(self) -> None:
        fake_status = TunnelStatus(
            provider=TunnelProvider.RELAY,
            active=True,
            url="wss://llmhosts.com/api/relay/connect",
        )
        mgr = TunnelManager()
        mgr._relay = AsyncMock()
        mgr._relay.status = AsyncMock(return_value=fake_status)
        mgr._active_provider = TunnelProvider.RELAY

        status = await mgr.status()
        assert status.active is True
        assert status.provider == TunnelProvider.RELAY

    @pytest.mark.asyncio
    async def test_restart(self) -> None:
        fake_status = TunnelStatus(
            provider=TunnelProvider.RELAY,
            active=True,
            url="wss://llmhosts.com/api/relay/connect",
            local_port=4000,
        )
        with (
            patch.object(TunnelDetector, "detect", return_value=[TunnelProvider.RELAY]),
            patch.object(RelayTunnel, "start", return_value=fake_status),
        ):
            mgr = TunnelManager()
            config = TunnelConfig(auth_required=False, relay_token="tok_abc")
            status = await mgr.restart(config)
            assert status.active is True
