"""Tunnel lifecycle orchestrator.

``TunnelManager`` is the main entry point for the tunnel subsystem.
It detects if the relay is available, starts/stops it, and manages auth.

The LLMHosts Relay is a built-in WebSocket reverse tunnel through
llmhosts.com. The device connects outbound to the relay server; the
relay forwards requests back through the WebSocket. No external
software is required — just internet access and a device token.

Usage::

    mgr = TunnelManager()
    status = await mgr.start()
    print(f"Your LLMHosts is accessible at: {status.url}")
    # ... later ...
    await mgr.stop()
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from llmhosts.tunnel.auth import TunnelAuth
from llmhosts.tunnel.detector import TunnelDetector
from llmhosts.tunnel.models import TunnelConfig, TunnelProvider, TunnelStatus
from llmhosts.tunnel.relay import RelayTunnel

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger(__name__)


class TunnelManager:
    """Orchestrate tunnel lifecycle.

    Uses the built-in LLMHosts Relay — no external software needed.
    """

    def __init__(self, config_dir: Path | None = None) -> None:
        self._config_dir = config_dir
        self._relay: RelayTunnel | None = None
        self._active_provider: TunnelProvider = TunnelProvider.NONE
        self._auth: TunnelAuth | None = None
        self._status: TunnelStatus = TunnelStatus()

    @property
    def auth(self) -> TunnelAuth | None:
        """The auth handler, available after :meth:`start`."""
        return self._auth

    @property
    def active_provider(self) -> TunnelProvider:
        """The currently active provider."""
        return self._active_provider

    async def start(self, config: TunnelConfig | None = None) -> TunnelStatus:
        """Start the relay tunnel.

        Checks that the Rust relay extension is available, then connects
        to the relay server using the configured URL and device token.
        """
        cfg = config or TunnelConfig()

        # Check relay availability
        available = await TunnelDetector.detect()
        if TunnelProvider.RELAY not in available:
            return TunnelStatus(
                provider=TunnelProvider.NONE,
                active=False,
                local_port=cfg.port,
                error="Relay not available (Rust extension not built)",
            )

        self._relay = RelayTunnel()
        status = await self._relay.start(
            port=cfg.port,
            relay_url=cfg.relay_url,
            device_token=cfg.relay_token,
        )

        if status.active:
            self._active_provider = TunnelProvider.RELAY
            self._status = status
            if cfg.auth_required:
                self._auth = await TunnelAuth.load(self._config_dir)
                logger.info("Tunnel auth enabled (key loaded)")

        return status

    async def stop(self) -> None:
        """Stop the relay tunnel and clean up resources."""
        if self._relay:
            await self._relay.stop()
            self._relay = None

        self._active_provider = TunnelProvider.NONE
        self._status = TunnelStatus()
        logger.info("Tunnel stopped")

    async def status(self) -> TunnelStatus:
        """Get current tunnel status."""
        if self._active_provider == TunnelProvider.RELAY and self._relay:
            self._status = await self._relay.status()
        else:
            self._status = TunnelStatus(provider=TunnelProvider.NONE, active=False)
        return self._status

    async def restart(self, config: TunnelConfig | None = None) -> TunnelStatus:
        """Stop and restart the tunnel."""
        await self.stop()
        return await self.start(config)
