"""
Tests for LinkedIn API client.

Tests the API client configuration and structure without making real API calls.

Note: These tests use linkedin_access_token for the LinkedInConfig/LinkedInClient classes,
which are still used internally by the MCP server after OAuth authentication.
The MCP server obtains tokens via OAuth and passes them to these classes.

For OAuth flow tests, see test_oauth.py and test_session_oauth.py.
"""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from chuk_mcp_linkedin.api import LinkedInAPIError, LinkedInClient, LinkedInConfig


class TestLinkedInConfig:
    """Test LinkedInConfig class"""

    def test_config_creation(self):
        """Test creating configuration"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        assert config.linkedin_access_token == "test_token"
        assert config.linkedin_person_urn == "urn:li:person:test123"

    def test_config_default_base_url(self):
        """Test default API base URL"""
        config = LinkedInConfig()
        assert config.linkedin_api_base_url == "https://api.linkedin.com/v2"

    def test_config_custom_base_url(self):
        """Test custom API base URL"""
        config = LinkedInConfig(linkedin_api_base_url="https://custom.api.com")
        assert config.linkedin_api_base_url == "https://custom.api.com"


class TestLinkedInClientInit:
    """Test LinkedInClient initialization"""

    def test_client_creation_with_config(self):
        """Test creating client with explicit config"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        client = LinkedInClient(config=config)
        assert client.access_token == "test_token"
        assert client.person_urn == "urn:li:person:test123"

    def test_client_creation_uses_default_config(self):
        """Test creating client without config uses default"""
        client = LinkedInClient()
        # Should not raise an error
        assert client.config is not None

    def test_client_has_base_url(self):
        """Test client has base URL"""
        config = LinkedInConfig(linkedin_api_base_url="https://api.linkedin.com/v2")
        client = LinkedInClient(config=config)
        assert client.base_url == "https://api.linkedin.com/v2"


class TestLinkedInClientHeaders:
    """Test LinkedIn client headers"""

    def test_get_headers_includes_authorization(self):
        """Test headers include authorization"""
        config = LinkedInConfig(linkedin_access_token="test_token")
        client = LinkedInClient(config=config)
        headers = client._get_headers()
        assert "Authorization" in headers
        assert headers["Authorization"] == "Bearer test_token"

    def test_get_headers_includes_content_type(self):
        """Test headers include content type"""
        config = LinkedInConfig(linkedin_access_token="test_token")
        client = LinkedInClient(config=config)
        headers = client._get_headers()
        assert "Content-Type" in headers
        assert headers["Content-Type"] == "application/json"

    def test_get_headers_includes_protocol_version(self):
        """Test headers include protocol version"""
        config = LinkedInConfig(linkedin_access_token="test_token")
        client = LinkedInClient(config=config)
        headers = client._get_headers()
        assert "X-Restli-Protocol-Version" in headers

    def test_get_headers_with_rest_api_flag(self):
        """Test headers include LinkedIn-Version when use_rest_api=True"""
        config = LinkedInConfig(linkedin_access_token="test_token")
        client = LinkedInClient(config=config)
        headers = client._get_headers(use_rest_api=True)
        assert "Linkedin-Version" in headers
        assert headers["Linkedin-Version"] == "202502"

    def test_get_headers_without_rest_api_flag(self):
        """Test headers don't include LinkedIn-Version when use_rest_api=False"""
        config = LinkedInConfig(linkedin_access_token="test_token")
        client = LinkedInClient(config=config)
        headers = client._get_headers(use_rest_api=False)
        assert "Linkedin-Version" not in headers


class TestLinkedInClientMixins:
    """Test that client has all mixins"""

    def test_client_has_posts_api(self):
        """Test client has posts API methods"""
        client = LinkedInClient()
        assert hasattr(client, "create_text_post")
        assert hasattr(client, "create_image_post")
        assert hasattr(client, "create_video_post")
        assert hasattr(client, "create_multi_image_post")
        assert hasattr(client, "create_poll_post")

    def test_client_has_media_api(self):
        """Test client has media API methods"""
        client = LinkedInClient()
        assert hasattr(client, "upload_image")
        assert hasattr(client, "upload_video")

    def test_client_has_documents_api(self):
        """Test client has documents API methods"""
        client = LinkedInClient()
        assert hasattr(client, "upload_document")
        assert hasattr(client, "create_document_post")


class TestLinkedInAPIError:
    """Test LinkedInAPIError exception"""

    def test_error_creation(self):
        """Test creating API error"""
        error = LinkedInAPIError("Test error message")
        assert str(error) == "Test error message"

    def test_error_is_exception(self):
        """Test error is an Exception"""
        error = LinkedInAPIError("Test")
        assert isinstance(error, Exception)

    def test_error_can_be_raised(self):
        """Test error can be raised"""
        with pytest.raises(LinkedInAPIError):
            raise LinkedInAPIError("Test error")

    def test_error_can_be_caught(self):
        """Test error can be caught"""
        try:
            raise LinkedInAPIError("Test error")
        except LinkedInAPIError as e:
            assert str(e) == "Test error"


class TestLinkedInClientValidation:
    """Test LinkedIn client configuration validation"""

    def test_validate_config_with_all_credentials(self):
        """Test validation succeeds with all credentials"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        client = LinkedInClient(config=config)
        is_valid, missing = client.validate_config()
        assert is_valid is True
        assert len(missing) == 0

    def test_validate_config_missing_token(self):
        """Test validation fails with missing token"""
        config = LinkedInConfig(
            linkedin_access_token=None,
            linkedin_person_urn="urn:li:person:test123",
        )
        client = LinkedInClient(config=config)
        is_valid, missing = client.validate_config()
        assert is_valid is False
        assert "linkedin_access_token" in missing

    def test_validate_config_missing_person_urn(self):
        """Test validation fails with missing person URN"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn=None,
        )
        client = LinkedInClient(config=config)
        is_valid, missing = client.validate_config()
        assert is_valid is False
        assert "linkedin_person_urn" in missing

    def test_validate_config_missing_both(self):
        """Test validation fails with both missing"""
        config = LinkedInConfig(
            linkedin_access_token=None,
            linkedin_person_urn=None,
        )
        client = LinkedInClient(config=config)
        is_valid, missing = client.validate_config()
        assert is_valid is False
        assert "linkedin_access_token" in missing
        assert "linkedin_person_urn" in missing
        assert len(missing) == 2


class TestLinkedInConfigMethods:
    """Test LinkedInConfig helper methods"""

    def test_is_configured_with_credentials(self):
        """Test is_configured returns True with credentials"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        assert config.is_configured() is True

    def test_is_configured_without_token(self):
        """Test is_configured returns False without token"""
        config = LinkedInConfig(
            linkedin_access_token=None,
            linkedin_person_urn="urn:li:person:test123",
        )
        assert config.is_configured() is False

    def test_is_configured_without_urn(self):
        """Test is_configured returns False without URN"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn=None,
        )
        assert config.is_configured() is False

    def test_is_configured_without_both(self):
        """Test is_configured returns False without either"""
        config = LinkedInConfig(
            linkedin_access_token=None,
            linkedin_person_urn=None,
        )
        assert config.is_configured() is False

    def test_get_missing_config_all_present(self):
        """Test get_missing_config returns empty list when all present"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        missing = config.get_missing_config()
        assert len(missing) == 0

    def test_get_missing_config_token_missing(self):
        """Test get_missing_config identifies missing token"""
        config = LinkedInConfig(
            linkedin_access_token=None,
            linkedin_person_urn="urn:li:person:test123",
        )
        missing = config.get_missing_config()
        assert "linkedin_access_token" in missing

    def test_get_missing_config_urn_missing(self):
        """Test get_missing_config identifies missing URN"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn=None,
        )
        missing = config.get_missing_config()
        assert "linkedin_person_urn" in missing

    def test_get_missing_config_both_missing(self):
        """Test get_missing_config identifies both missing"""
        config = LinkedInConfig(
            linkedin_access_token=None,
            linkedin_person_urn=None,
        )
        missing = config.get_missing_config()
        assert "linkedin_access_token" in missing
        assert "linkedin_person_urn" in missing


class TestLinkedInClientConnection:
    """Test LinkedIn client connection testing"""

    @pytest.mark.asyncio
    async def test_connection_success(self):
        """Test successful connection"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        client = LinkedInClient(config=config)

        with patch("httpx.AsyncClient") as mock_client_class:
            mock_client = AsyncMock()
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_client.get = AsyncMock(return_value=mock_response)
            mock_client.__aenter__ = AsyncMock(return_value=mock_client)
            mock_client.__aexit__ = AsyncMock(return_value=None)
            mock_client_class.return_value = mock_client

            result = await client.test_connection()
            assert result is True

    @pytest.mark.asyncio
    async def test_connection_no_token(self):
        """Test connection fails without token"""
        config = LinkedInConfig(linkedin_access_token=None)
        client = LinkedInClient(config=config)

        result = await client.test_connection()
        assert result is False

    @pytest.mark.asyncio
    async def test_connection_http_error(self):
        """Test connection fails on HTTP error"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        client = LinkedInClient(config=config)

        with patch("httpx.AsyncClient") as mock_client_class:
            mock_client = AsyncMock()
            mock_response = MagicMock()
            mock_response.status_code = 401
            mock_client.get = AsyncMock(return_value=mock_response)
            mock_client.__aenter__ = AsyncMock(return_value=mock_client)
            mock_client.__aexit__ = AsyncMock(return_value=None)
            mock_client_class.return_value = mock_client

            result = await client.test_connection()
            assert result is False

    @pytest.mark.asyncio
    async def test_connection_exception(self):
        """Test connection fails on exception"""
        config = LinkedInConfig(
            linkedin_access_token="test_token",
            linkedin_person_urn="urn:li:person:test123",
        )
        client = LinkedInClient(config=config)

        with patch("httpx.AsyncClient") as mock_client_class:
            mock_client = AsyncMock()
            mock_client.get = AsyncMock(side_effect=Exception("Connection error"))
            mock_client.__aenter__ = AsyncMock(return_value=mock_client)
            mock_client.__aexit__ = AsyncMock(return_value=None)
            mock_client_class.return_value = mock_client

            result = await client.test_connection()
            assert result is False
