"""LinkedIn API mock helper for testing.

This module provides a helper class for mocking LinkedIn API responses
in tests using unittest.mock. It simplifies setting up expected
responses for OAuth flows, organization queries, and notifications.

Usage:
    from integrations.tests.linkedin_api_mock import LinkedInAPIMock

    def test_oauth_flow(self):
        with LinkedInAPIMock() as mock:
            mock.setup_oauth_token_exchange()

            # Your test code that calls LinkedIn API
            client = LinkedInClient()
            token = client.exchange_code_for_token("auth_code")

            assert token.access_token == "AQVg5rBqEsI3fBdX..."
"""

from typing import Any
from unittest.mock import patch

from integrations.tests.fixtures.linkedin_error_fixtures import (
    mock_forbidden_response,
    mock_not_found_response,
    mock_rate_limit_response,
    mock_unauthorized_response,
)
from integrations.tests.fixtures.linkedin_notification_fixtures import (
    mock_empty_notifications_response,
    mock_notifications_response,
)
from integrations.tests.fixtures.linkedin_oauth_fixtures import (
    mock_oauth_error_response,
    mock_oauth_token_response,
    mock_token_refresh_response,
)
from integrations.tests.fixtures.linkedin_org_fixtures import (
    ORG_SCENARIOS,
    mock_organization_details,
    mock_organizations_response,
)


class MockResponse:
    """Mock HTTP response object."""

    def __init__(
        self,
        status_code: int,
        json_data: dict[str, Any] | None = None,
        headers: dict[str, str] | None = None,
        raise_for_status_error: Exception | None = None,
    ):
        self.status_code = status_code
        self._json_data = json_data or {}
        self.headers = headers or {}
        self._raise_for_status_error = raise_for_status_error
        self.ok = 200 <= status_code < 300

    def json(self) -> dict[str, Any]:
        return self._json_data

    def raise_for_status(self) -> None:
        if self._raise_for_status_error:
            raise self._raise_for_status_error


class LinkedInAPIMock:
    """Context manager for mocking LinkedIn API calls.

    This class patches requests.request to intercept HTTP calls
    and return mock responses based on the configured setup.

    Example:
        with LinkedInAPIMock() as mock:
            mock.setup_oauth_token_exchange()
            mock.setup_organizations(["acme_corp"])
            mock.setup_notifications(count=10)

            # Test code that calls LinkedIn API
    """

    def __init__(self):
        self._responses: list[tuple[str, str, MockResponse]] = []
        self._patcher = None
        self._mock = None
        self._call_index = 0

    def __enter__(self) -> "LinkedInAPIMock":
        self._patcher = patch("requests.request")
        self._mock = self._patcher.start()
        self._mock.side_effect = self._handle_request
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        if self._patcher:
            self._patcher.stop()
        return None

    def _handle_request(self, method: str, url: str, **kwargs) -> MockResponse:
        """Handle mocked requests by matching against configured responses."""
        for resp_method, resp_url_pattern, response in self._responses:
            if method.upper() == resp_method.upper() and resp_url_pattern in url:
                return response

        # Default: return 404 for unconfigured endpoints
        return MockResponse(
            status_code=404,
            json_data={"message": f"No mock configured for {method} {url}"},
        )

    def add_response(
        self,
        method: str,
        url_pattern: str,
        status_code: int = 200,
        json_data: dict[str, Any] | None = None,
        headers: dict[str, str] | None = None,
    ) -> "LinkedInAPIMock":
        """Add a mock response for a specific endpoint.

        Args:
            method: HTTP method (GET, POST, etc.)
            url_pattern: Substring to match in the URL
            status_code: Response status code
            json_data: Response JSON body
            headers: Response headers

        Returns:
            Self for chaining
        """
        response = MockResponse(
            status_code=status_code,
            json_data=json_data,
            headers=headers,
        )
        self._responses.append((method, url_pattern, response))
        return self

    # ==========================================================================
    # OAuth Setup Methods
    # ==========================================================================

    def setup_oauth_token_exchange(
        self,
        access_token: str = "AQVg5rBqEsI3fBdX...",
        refresh_token: str = "AQT8vU7BqEsI3fBdX...",
        expires_in: int = 5184000,
    ) -> "LinkedInAPIMock":
        """Set up mock for OAuth token exchange."""
        return self.add_response(
            "POST",
            "oauth/v2/accessToken",
            status_code=200,
            json_data=mock_oauth_token_response(
                access_token=access_token,
                refresh_token=refresh_token,
                expires_in=expires_in,
            ),
        )

    def setup_oauth_token_error(
        self,
        error: str = "invalid_grant",
        error_description: str = "The authorization code has expired",
    ) -> "LinkedInAPIMock":
        """Set up mock for OAuth token exchange error."""
        return self.add_response(
            "POST",
            "oauth/v2/accessToken",
            status_code=400,
            json_data=mock_oauth_error_response(
                error=error,
                error_description=error_description,
            ),
        )

    def setup_token_refresh(
        self,
        access_token: str = "AQVnew_refreshed_token...",
        refresh_token: str = "AQTnew_refresh_token...",
    ) -> "LinkedInAPIMock":
        """Set up mock for token refresh."""
        return self.add_response(
            "POST",
            "oauth/v2/accessToken",
            status_code=200,
            json_data=mock_token_refresh_response(
                access_token=access_token,
                refresh_token=refresh_token,
            ),
        )

    # ==========================================================================
    # Organizations Setup Methods
    # ==========================================================================

    def setup_organizations(
        self,
        organization_keys: list[str] | None = None,
    ) -> "LinkedInAPIMock":
        """Set up mock for organizations list."""
        return self.add_response(
            "GET",
            "organizationAcls",
            status_code=200,
            json_data=mock_organizations_response(organization_keys),
        )

    def setup_organization_details(
        self,
        organization_key: str = "acme_corp",
    ) -> "LinkedInAPIMock":
        """Set up mock for organization details."""
        return self.add_response(
            "GET",
            "organizations/",
            status_code=200,
            json_data=mock_organization_details(organization_key),
        )

    def setup_organizations_empty(self) -> "LinkedInAPIMock":
        """Set up mock for empty organizations response."""
        return self.add_response(
            "GET",
            "organizationAcls",
            status_code=200,
            json_data=ORG_SCENARIOS["empty"],
        )

    # ==========================================================================
    # Notifications Setup Methods
    # ==========================================================================

    def setup_notifications(
        self,
        count: int = 5,
        action_types: list[str] | None = None,
        organization_urn: str = "urn:li:organization:12345",
    ) -> "LinkedInAPIMock":
        """Set up mock for notifications endpoint."""
        return self.add_response(
            "GET",
            "organizationSocialFeedData",
            status_code=200,
            json_data=mock_notifications_response(
                count=count,
                action_types=action_types,
                organization_urn=organization_urn,
            ),
        )

    def setup_notifications_empty(self) -> "LinkedInAPIMock":
        """Set up mock for empty notifications response."""
        return self.add_response(
            "GET",
            "organizationSocialFeedData",
            status_code=200,
            json_data=mock_empty_notifications_response(),
        )

    def setup_notifications_paginated(
        self,
        pages: list[int],
        organization_urn: str = "urn:li:organization:12345",
    ) -> "LinkedInAPIMock":
        """Set up mock for paginated notifications.

        Args:
            pages: List of counts for each page (e.g., [10, 10, 5] for 3 pages)
            organization_urn: Organization URN

        Note: This is a simplified version. For proper pagination testing,
              you may need to use a custom side_effect function.
        """
        start = 0
        for page_count in pages:
            self.add_response(
                "GET",
                "organizationSocialFeedData",
                status_code=200,
                json_data=mock_notifications_response(
                    count=page_count,
                    organization_urn=organization_urn,
                    start=start,
                ),
            )
            start += page_count
        return self

    # ==========================================================================
    # Error Setup Methods
    # ==========================================================================

    def setup_rate_limit(
        self,
        retry_after: int = 60,
        endpoint: str = "organizationSocialFeedData",
    ) -> "LinkedInAPIMock":
        """Set up mock for rate limit response."""
        status, headers, body = mock_rate_limit_response(retry_after)
        return self.add_response(
            "GET",
            endpoint,
            status_code=status,
            json_data=body,
            headers=headers,
        )

    def setup_token_expired(
        self,
        endpoint: str = "organizationSocialFeedData",
    ) -> "LinkedInAPIMock":
        """Set up mock for expired token response."""
        status, headers, body = mock_unauthorized_response(
            error_code="EXPIRED_ACCESS_TOKEN",
            message="The access token has expired.",
        )
        return self.add_response(
            "GET",
            endpoint,
            status_code=status,
            json_data=body,
            headers=headers,
        )

    def setup_forbidden(
        self,
        endpoint: str = "organizationSocialFeedData",
        message: str = "The user does not have permission to access this resource.",
    ) -> "LinkedInAPIMock":
        """Set up mock for forbidden response."""
        status, headers, body = mock_forbidden_response(message=message)
        return self.add_response(
            "GET",
            endpoint,
            status_code=status,
            json_data=body,
            headers=headers,
        )

    def setup_not_found(
        self,
        resource_type: str = "organization",
        resource_id: str = "urn:li:organization:99999",
    ) -> "LinkedInAPIMock":
        """Set up mock for not found response."""
        status, headers, body = mock_not_found_response(resource_type, resource_id)
        return self.add_response(
            "GET",
            "organizations/",
            status_code=status,
            json_data=body,
            headers=headers,
        )

    # ==========================================================================
    # Preset Scenarios
    # ==========================================================================

    def setup_full_oauth_flow(self) -> "LinkedInAPIMock":
        """Set up mocks for a complete OAuth flow."""
        return self.setup_oauth_token_exchange().setup_organizations(["acme_corp", "tech_startup"])

    def setup_sync_scenario(
        self,
        notification_count: int = 10,
    ) -> "LinkedInAPIMock":
        """Set up mocks for a typical sync operation."""
        return self.setup_notifications(count=notification_count)

    def setup_error_recovery_scenario(self) -> "LinkedInAPIMock":
        """Set up mocks for testing error recovery.

        First call fails with rate limit, subsequent calls succeed.
        """
        # This requires custom side_effect handling
        # For now, just set up the success response
        return self.setup_notifications(count=5)


# Convenience function for simple cases
def mock_linkedin_api(**kwargs) -> LinkedInAPIMock:
    """Create a configured LinkedInAPIMock.

    Args:
        **kwargs: Setup methods to call (e.g., oauth=True, notifications=10)

    Returns:
        Configured LinkedInAPIMock

    Example:
        with mock_linkedin_api(oauth=True, notifications=5) as mock:
            # Test code
    """
    mock = LinkedInAPIMock()

    if kwargs.get("oauth"):
        mock.setup_oauth_token_exchange()

    if "notifications" in kwargs:
        mock.setup_notifications(count=kwargs["notifications"])

    if kwargs.get("organizations"):
        mock.setup_organizations(kwargs.get("organization_keys"))

    return mock
