"""Authentication for tunnel access.

When LLMHosts is accessible via a tunnel, we need to verify that the
remote client is authorised.  A shared API key (prefixed ``lh-``) is
generated on first use and stored in ``~/.llmhosts/tunnel_key``.

The key is shown to the user on ``llmhosts tunnel`` start so they can
configure their remote client with::

    OPENAI_API_KEY=lh-<token>
"""

from __future__ import annotations

import json
import logging
import secrets
from pathlib import Path

from llmhosts.constants import DATA_DIR_NAME

logger = logging.getLogger(__name__)

_KEY_FILENAME = "tunnel_key.json"


class TunnelAuth:
    """Generate, store, and verify tunnel API keys."""

    def __init__(self, api_key: str | None = None) -> None:
        self._api_key: str = api_key or self._generate_key()

    # -- Key generation -------------------------------------------------------

    @staticmethod
    def _generate_key() -> str:
        """Generate a random API key with an ``lh-`` prefix."""
        return f"lh-{secrets.token_urlsafe(32)}"

    # -- Verification ---------------------------------------------------------

    def verify(self, provided_key: str) -> bool:
        """Return ``True`` if *provided_key* matches the stored key."""
        return secrets.compare_digest(self._api_key, provided_key.strip())

    # -- Accessors ------------------------------------------------------------

    def get_key(self) -> str:
        """Return the current API key."""
        return self._api_key

    # -- Persistence ----------------------------------------------------------

    async def save(self, config_dir: Path | None = None) -> None:
        """Persist the key to ``config_dir/tunnel_key.json``.

        Uses the default ``~/.llmhosts`` directory when *config_dir* is ``None``.
        """
        import asyncio

        directory = config_dir or (Path.home() / DATA_DIR_NAME)
        directory.mkdir(parents=True, exist_ok=True)
        path = directory / _KEY_FILENAME

        loop = asyncio.get_running_loop()

        def _write() -> None:
            data = {"api_key": self._api_key}
            path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
            # Restrict permissions to owner only
            import contextlib

            with contextlib.suppress(OSError):
                path.chmod(0o600)

        await loop.run_in_executor(None, _write)
        logger.debug("Tunnel key saved to %s", path)

    @classmethod
    async def load(cls, config_dir: Path | None = None) -> TunnelAuth:
        """Load a key from ``config_dir/tunnel_key.json``, or generate a new one.

        If the file does not exist or is invalid, a fresh key is created
        and saved automatically.
        """
        import asyncio

        directory = config_dir or (Path.home() / DATA_DIR_NAME)
        path = directory / _KEY_FILENAME

        loop = asyncio.get_running_loop()

        def _read() -> str | None:
            if not path.exists():
                return None
            try:
                data = json.loads(path.read_text(encoding="utf-8"))
                key: str = data.get("api_key", "")
                if key and key.startswith("lh-"):
                    return str(key)
            except (json.JSONDecodeError, OSError) as exc:
                logger.debug("Failed to read tunnel key from %s: %s", path, exc)
            return None

        existing_key = await loop.run_in_executor(None, _read)

        if existing_key:
            logger.debug("Loaded existing tunnel key from %s", path)
            return cls(api_key=existing_key)

        # Generate fresh key and persist it
        auth = cls()
        await auth.save(config_dir)
        logger.info("Generated new tunnel key and saved to %s", path)
        return auth
