"""Tests for client factory module."""

import os
from unittest.mock import patch

import pytest

from crustdata_mcp.client_factory import (
    ClientFactory,
    get_client_factory,
    get_client_for_request,
    reset_factory,
)


class TestClientFactory:
    """Tests for ClientFactory class."""

    def setup_method(self):
        """Reset factory before each test."""
        reset_factory()

    def teardown_method(self):
        """Reset factory after each test."""
        reset_factory()

    def test_stdio_mode_with_env_token(self):
        """Test STDIO mode uses env var token."""
        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "test-token-stdio"}):
            factory = ClientFactory()
            client = factory._get_stdio_client()

            assert client is not None
            assert client.api_token == "test-token-stdio"

    def test_stdio_mode_without_token_raises(self):
        """Test STDIO mode raises without token."""
        with patch.dict(os.environ, {}, clear=True):
            os.environ.pop("CRUSTDATA_TOKEN", None)
            factory = ClientFactory()

            with pytest.raises(ValueError, match="No authentication token available"):
                factory._get_stdio_client()

    def test_error_message_mentions_both_transports(self):
        """Test error message mentions both STDIO and HTTP options."""
        with patch.dict(os.environ, {}, clear=True):
            os.environ.pop("CRUSTDATA_TOKEN", None)
            factory = ClientFactory()

            with pytest.raises(ValueError) as exc_info:
                factory._get_stdio_client()

            error_msg = str(exc_info.value)
            assert "CRUSTDATA_TOKEN" in error_msg
            assert "Authorization: Bearer" in error_msg

    def test_client_caching_same_token(self):
        """Test that same token returns cached client."""
        factory = ClientFactory()
        client1 = factory._get_or_create_client("token-abc")
        client2 = factory._get_or_create_client("token-abc")

        assert client1 is client2

    def test_client_caching_different_tokens(self):
        """Test that different tokens get different clients."""
        factory = ClientFactory()
        client1 = factory._get_or_create_client("token-abc")
        client2 = factory._get_or_create_client("token-xyz")

        assert client1 is not client2
        assert client1.api_token == "token-abc"
        assert client2.api_token == "token-xyz"

    def test_stdio_client_caching(self):
        """Test that STDIO client is cached."""
        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "test-token"}):
            factory = ClientFactory()
            client1 = factory._get_stdio_client()
            client2 = factory._get_stdio_client()

            assert client1 is client2

    def test_stdio_client_recreated_on_token_change(self):
        """Test that STDIO client is recreated if env token changes."""
        factory = ClientFactory()

        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "token-1"}):
            client1 = factory._get_stdio_client()

        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "token-2"}):
            client2 = factory._get_stdio_client()

        assert client1 is not client2
        assert client1.api_token == "token-1"
        assert client2.api_token == "token-2"

    @pytest.mark.asyncio
    async def test_close_all_clears_clients(self):
        """Test that close_all clears all cached clients."""
        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "test-token"}):
            factory = ClientFactory()

            # Create some clients
            _ = factory._get_or_create_client("token-1")
            _ = factory._get_or_create_client("token-2")
            _ = factory._get_stdio_client()

            await factory.close_all()

            assert factory._stdio_client is None
            assert factory._stdio_token is None


class TestGetClientFactory:
    """Tests for get_client_factory singleton function."""

    def setup_method(self):
        """Reset factory before each test."""
        reset_factory()

    def teardown_method(self):
        """Reset factory after each test."""
        reset_factory()

    def test_returns_same_instance(self):
        """Test that get_client_factory returns singleton."""
        factory1 = get_client_factory()
        factory2 = get_client_factory()

        assert factory1 is factory2

    def test_reset_factory_clears_singleton(self):
        """Test that reset_factory clears the singleton."""
        factory1 = get_client_factory()
        reset_factory()
        factory2 = get_client_factory()

        assert factory1 is not factory2


class TestGetClientForRequest:
    """Tests for get_client_for_request convenience function."""

    def setup_method(self):
        """Reset factory before each test."""
        reset_factory()

    def teardown_method(self):
        """Reset factory after each test."""
        reset_factory()

    def test_stdio_fallback(self):
        """Test that get_client_for_request falls back to STDIO mode."""
        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "stdio-token"}):
            # Without HTTP context, should fall back to STDIO
            client = get_client_for_request()

            assert client is not None
            assert client.api_token == "stdio-token"

    def test_raises_without_token(self):
        """Test that get_client_for_request raises without any token."""
        with patch.dict(os.environ, {}, clear=True):
            os.environ.pop("CRUSTDATA_TOKEN", None)

            with pytest.raises(ValueError, match="No authentication token"):
                get_client_for_request()


class TestTransportMode:
    """Tests for transport mode functionality."""

    def setup_method(self):
        """Reset factory before each test."""
        reset_factory()

    def teardown_method(self):
        """Reset factory after each test."""
        reset_factory()

    def test_set_transport_mode_stdio(self):
        """Test setting transport mode to stdio."""
        factory = ClientFactory()
        factory.set_transport_mode("stdio")

        assert factory._transport_mode == "stdio"

    def test_set_transport_mode_http(self):
        """Test setting transport mode to http."""
        factory = ClientFactory()
        factory.set_transport_mode("http")

        assert factory._transport_mode == "http"

    def test_http_mode_fails_without_token(self):
        """Test that HTTP mode fails explicitly without token in context."""
        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "env-token"}):
            factory = ClientFactory()
            factory.set_transport_mode("http")

            # Even though env token exists, HTTP mode should fail without context token
            with pytest.raises(ValueError, match="No authentication token in request context"):
                factory.get_client_for_request()

    def test_http_mode_error_message(self):
        """Test that HTTP mode error message is helpful."""
        factory = ClientFactory()
        factory.set_transport_mode("http")

        with pytest.raises(ValueError) as exc_info:
            factory.get_client_for_request()

        error_msg = str(exc_info.value)
        assert "No authentication token" in error_msg
        assert "reconnect" in error_msg

    def test_stdio_mode_falls_back_to_env(self):
        """Test that STDIO mode falls back to env var token."""
        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "stdio-env-token"}):
            factory = ClientFactory()
            factory.set_transport_mode("stdio")

            # Without HTTP context, should fall back to env var
            client = factory.get_client_for_request()

            assert client is not None
            assert client.api_token == "stdio-env-token"

    def test_unset_mode_falls_back_to_env(self):
        """Test backward compatibility: unset mode falls back to env var."""
        with patch.dict(os.environ, {"CRUSTDATA_TOKEN": "fallback-token"}):
            factory = ClientFactory()
            # Don't set transport mode (backward compatibility)

            client = factory.get_client_for_request()

            assert client is not None
            assert client.api_token == "fallback-token"

    def test_transport_mode_initially_none(self):
        """Test that transport mode is None initially."""
        factory = ClientFactory()

        assert factory._transport_mode is None
