"""Coverage gap tests for experiences/server.py — lines 57-58, 257-258, 273-274."""

from __future__ import annotations

import socket
import threading
import time
import urllib.error
import urllib.request
from http.server import HTTPServer

from orxaq.enterprise.auth import APIKeyStore, AuthMiddleware, RateLimiter, TokenProvider
from orxaq.experiences.api import OrxaqAPI
from orxaq.experiences.editor import DAGEditorAPI
from orxaq.experiences.server import OrxaqHandler, _make_handler


def _find_free_port() -> int:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("localhost", 0))
        return s.getsockname()[1]


def _get(url: str, headers: dict | None = None) -> tuple[int, bytes]:
    req = urllib.request.Request(url, headers=headers or {})
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.status, resp.read()
    except urllib.error.HTTPError as e:
        return e.code, e.read()


class TestRateLimitedPath:
    """Lines 57-58: rate_limited returns 429."""

    @classmethod
    def setup_class(cls) -> None:
        cls.port = _find_free_port()
        api = OrxaqAPI()
        editor = DAGEditorAPI(api.engine)
        key_store = APIKeyStore()
        token_provider = TokenProvider()
        # Capacity=1, no refill — second request is rate-limited
        rate_limiter = RateLimiter(capacity=1, refill_rate=0.0)
        OrxaqHandler.auth = AuthMiddleware(
            key_store=key_store,
            token_provider=token_provider,
            rate_limiter=rate_limiter,
        )
        raw_key, _ = key_store.generate_key(name="test-rl")
        cls.raw_key = raw_key

        def handler(*a, **kw):
            return _make_handler(api, editor, *a, **kw)

        cls.server = HTTPServer(("localhost", cls.port), handler)
        cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
        cls.thread.start()
        time.sleep(0.3)
        cls.base = f"http://localhost:{cls.port}"

    @classmethod
    def teardown_class(cls) -> None:
        cls.server.shutdown()
        OrxaqHandler.auth = None

    def test_first_request_allowed(self) -> None:
        status, _ = _get(self.base + "/api/status", {"X-API-Key": self.raw_key})
        assert status == 200

    def test_second_request_rate_limited(self) -> None:
        """Lines 57-58: rate_limiter exhausted → 429."""
        # First request consumes the single token
        _get(self.base + "/api/status", {"X-API-Key": self.raw_key})
        # Second request hits rate limit
        status, body = _get(self.base + "/api/status", {"X-API-Key": self.raw_key})
        assert status == 429


class TestSkinNotFound:
    """Lines 257-258: get_skin returns None → fallback to gamma.html."""

    @classmethod
    def setup_class(cls) -> None:
        cls.port = _find_free_port()
        api = OrxaqAPI()
        editor = DAGEditorAPI(api.engine)
        OrxaqHandler.auth = None

        def handler(*a, **kw):
            return _make_handler(api, editor, *a, **kw)

        cls.server = HTTPServer(("localhost", cls.port), handler)
        cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
        cls.thread.start()
        time.sleep(0.3)
        cls.base = f"http://localhost:{cls.port}"

    @classmethod
    def teardown_class(cls) -> None:
        cls.server.shutdown()

    def test_unknown_skin_falls_back(self) -> None:
        """Lines 257-258: unknown skin name → fallback serve (no crash)."""
        status, body = _get(self.base + "/?skin=nonexistent_skin_xyz")
        # Should return 200 (fallback) or 404 (file not found) — no 500
        assert status in (200, 404)


class TestServeFileMissing:
    """Lines 273-274: static file doesn't exist → 404."""

    @classmethod
    def setup_class(cls) -> None:
        cls.port = _find_free_port()
        api = OrxaqAPI()
        editor = DAGEditorAPI(api.engine)
        OrxaqHandler.auth = None

        def handler(*a, **kw):
            return _make_handler(api, editor, *a, **kw)

        cls.server = HTTPServer(("localhost", cls.port), handler)
        cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
        cls.thread.start()
        time.sleep(0.3)
        cls.base = f"http://localhost:{cls.port}"

    @classmethod
    def teardown_class(cls) -> None:
        cls.server.shutdown()

    def test_missing_static_file_returns_404(self) -> None:
        """Lines 273-274: request for nonexistent static file → 404."""
        status, body = _get(self.base + "/this_file_does_not_exist_at_all.xyz")
        assert status == 404
