"""Tests for UnityInstanceMiddleware auth enforcement in remote-hosted mode."""

import asyncio
import sys
from unittest.mock import AsyncMock, Mock, patch

import pytest

from core.config import config
from tests.integration.test_helpers import DummyContext


class TestMiddlewareAuthEnforcement:
    @pytest.mark.asyncio
    async def test_remote_hosted_requires_user_id(self, monkeypatch):
        """_inject_unity_instance should raise RuntimeError when remote-hosted and no user_id."""
        monkeypatch.setattr(config, "http_remote_hosted", True)

        from transport.unity_instance_middleware import UnityInstanceMiddleware

        middleware = UnityInstanceMiddleware()

        # Mock _resolve_user_id to return None (no API key / failed validation)
        monkeypatch.setattr(middleware, "_resolve_user_id",
                            AsyncMock(return_value=None))

        ctx = DummyContext()
        middleware_ctx = Mock()
        middleware_ctx.fastmcp_context = ctx

        with pytest.raises(RuntimeError, match="API key authentication required"):
            await middleware._inject_unity_instance(middleware_ctx)

    @pytest.mark.asyncio
    async def test_sets_user_id_in_context_state(self, monkeypatch):
        """_inject_unity_instance should set user_id in ctx state when resolved."""
        monkeypatch.setattr(config, "http_remote_hosted", True)

        from transport.unity_instance_middleware import UnityInstanceMiddleware

        middleware = UnityInstanceMiddleware()
        monkeypatch.setattr(middleware, "_resolve_user_id",
                            AsyncMock(return_value="user-55"))

        # We need PluginHub to be configured for the session resolution path
        # But we don't need it to actually find a session for this test
        from transport.plugin_hub import PluginHub
        from transport.plugin_registry import PluginRegistry

        registry = PluginRegistry()
        loop = asyncio.get_running_loop()
        PluginHub.configure(registry, loop)

        ctx = DummyContext()
        ctx.client_id = "client-1"
        middleware_ctx = Mock()
        middleware_ctx.fastmcp_context = ctx

        # Set an active instance so the middleware doesn't try to auto-select
        await middleware.set_active_instance(ctx, "Proj@hash1")
        # Register a matching session so resolution doesn't fail
        await registry.register("s1", "Proj", "hash1", "2022", user_id="user-55")

        await middleware._inject_unity_instance(middleware_ctx)

        assert await ctx.get_state("user_id") == "user-55"


class TestMiddlewareSessionKey:
    @pytest.mark.asyncio
    async def test_get_session_key_uses_user_id_fallback(self):
        """When no client_id, middleware should use user:$user_id as session key."""
        from transport.unity_instance_middleware import UnityInstanceMiddleware

        middleware = UnityInstanceMiddleware()

        ctx = DummyContext()
        # Simulate no client_id attribute
        if hasattr(ctx, "client_id"):
            delattr(ctx, "client_id")
        await ctx.set_state("user_id", "user-77")

        key = await middleware.get_session_key(ctx)
        assert key == "user:user-77"

    @pytest.mark.asyncio
    async def test_get_session_key_prefers_client_id(self):
        """client_id should take precedence over user_id."""
        from transport.unity_instance_middleware import UnityInstanceMiddleware

        middleware = UnityInstanceMiddleware()

        ctx = DummyContext()
        ctx.client_id = "client-abc"
        await ctx.set_state("user_id", "user-77")

        key = await middleware.get_session_key(ctx)
        assert key == "client-abc"


class TestAutoSelectDisabledRemoteHosted:
    @pytest.mark.asyncio
    async def test_auto_select_returns_none_in_remote_hosted(self, monkeypatch):
        """_maybe_autoselect_instance should return None in remote-hosted mode even with one session."""
        monkeypatch.setattr(config, "http_remote_hosted", True)
        monkeypatch.setattr(config, "transport_mode", "http")

        # Re-import middleware to pick up the stubbed transport module
        monkeypatch.delitem(
            sys.modules, "transport.unity_instance_middleware", raising=False)
        from transport.unity_instance_middleware import UnityInstanceMiddleware, PluginHub as HubRef

        # Configure PluginHub with one session so auto-select has something to find
        from transport.plugin_registry import PluginRegistry
        registry = PluginRegistry()
        await registry.register("s1", "Proj", "h1", "2022", user_id="userA")

        loop = asyncio.get_running_loop()
        HubRef.configure(registry, loop)

        middleware = UnityInstanceMiddleware()
        ctx = DummyContext()
        ctx.client_id = "client-1"

        result = await middleware._maybe_autoselect_instance(ctx)
        # Remote-hosted mode should NOT auto-select (early return at the transport check)
        assert result is None


class TestHttpAuthBehavior:
    @pytest.mark.asyncio
    async def test_http_local_does_not_require_user_id(self, monkeypatch):
        """HTTP local mode should allow requests without user_id."""
        monkeypatch.setattr(config, "http_remote_hosted", False)
        monkeypatch.setattr(config, "transport_mode", "http")

        from transport import unity_transport

        async def fake_send_command_for_instance(*_args, **_kwargs):
            return {"success": True, "data": {"ok": True}}

        monkeypatch.setattr(
            unity_transport.PluginHub,
            "send_command_for_instance",
            fake_send_command_for_instance,
        )

        async def _unused_send_fn(*_args, **_kwargs):
            raise AssertionError("send_fn should not be used in HTTP mode")

        result = await unity_transport.send_with_unity_instance(
            _unused_send_fn, None, "ping", {}
        )

        assert result["success"] is True
        assert result["data"] == {"ok": True}

    @pytest.mark.asyncio
    async def test_http_remote_requires_user_id(self, monkeypatch):
        """HTTP remote-hosted mode should reject requests without user_id."""
        monkeypatch.setattr(config, "http_remote_hosted", True)
        monkeypatch.setattr(config, "transport_mode", "http")

        from transport import unity_transport

        async def _unused_send_fn(*_args, **_kwargs):
            raise AssertionError("send_fn should not be used in HTTP mode")

        result = await unity_transport.send_with_unity_instance(
            _unused_send_fn, None, "ping", {}
        )

        assert result["success"] is False
        assert result["error"] == "auth_required"
