"""
OAuth metadata extraction utilities.

This module provides utilities for extracting user information
from OAuth responses across different providers.

Supported providers:
- Salesforce (instance_url, organization_id, user_id, is_sandbox)
- Atlassian/Jira (cloud_id, site_url, account_id)
- Microsoft (tenant_id, user_principal_name, object_id)
- Slack (team_id, team_name, user_id)
- Google (sub, email, name)
- Generic (user_id, email from common fields)

Example:
    from empowernow_common.vault.user_oauth.metadata import OAuthMetadataExtractor

    # Extract Salesforce metadata
    metadata = OAuthMetadataExtractor.extract_metadata("salesforce", token_response)
    print(metadata["instance_url"])  # https://myorg.salesforce.com

    # Generic extraction
    metadata = OAuthMetadataExtractor.build_metadata(token_response, userinfo)
"""

import logging
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)


class OAuthMetadataExtractionError(Exception):
    """
    Raised when critical metadata extraction fails.

    Some providers have critical metadata (like Salesforce's instance_url)
    that must be extracted for the integration to work.
    """

    def __init__(self, provider: str, message: str):
        self.provider = provider
        self.message = message
        super().__init__(f"{provider}: {message}")


class OAuthMetadataExtractor:
    """
    Extract user metadata from OAuth token responses.

    Different OAuth providers return user information in different
    formats. This class normalizes the extraction with both:
    - Generic methods for common fields (user_id, email)
    - Provider-specific methods for critical provider data

    Provider-specific extractors handle the unique response formats
    of each OAuth provider (Salesforce, Microsoft, Slack, etc.).
    """

    # =========================================================================
    # GENERIC EXTRACTION METHODS
    # =========================================================================

    @staticmethod
    def extract_user_id(
        token_response: Dict[str, Any],
        userinfo: Optional[Dict[str, Any]] = None,
    ) -> Optional[str]:
        """
        Extract user ID from OAuth response.

        Checks common fields: sub, user_id, id, uid

        Args:
            token_response: OAuth token response
            userinfo: Optional userinfo endpoint response

        Returns:
            User ID string or None
        """
        # Check userinfo first (more reliable)
        if userinfo:
            for field in ["sub", "user_id", "id", "uid"]:
                if field in userinfo:
                    return str(userinfo[field])

        # Check token response (some providers include claims)
        for field in ["sub", "user_id", "id", "uid"]:
            if field in token_response:
                return str(token_response[field])

        return None

    @staticmethod
    def extract_email(
        token_response: Dict[str, Any],
        userinfo: Optional[Dict[str, Any]] = None,
    ) -> Optional[str]:
        """
        Extract email from OAuth response.

        Args:
            token_response: OAuth token response
            userinfo: Optional userinfo endpoint response

        Returns:
            Email string or None
        """
        if userinfo and "email" in userinfo:
            return userinfo["email"]

        if "email" in token_response:
            return token_response["email"]

        return None

    @staticmethod
    def extract_instance_url(
        token_response: Dict[str, Any],
    ) -> Optional[str]:
        """
        Extract instance URL (for Salesforce-style providers).

        Args:
            token_response: OAuth token response

        Returns:
            Instance URL string or None
        """
        return token_response.get("instance_url")

    @staticmethod
    def build_metadata(
        token_response: Dict[str, Any],
        userinfo: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Build generic metadata dict from OAuth response.

        Use this for unknown providers or when you just need
        basic user info (user_id, email).

        Args:
            token_response: OAuth token response
            userinfo: Optional userinfo endpoint response

        Returns:
            Metadata dictionary with common fields
        """
        metadata: Dict[str, Any] = {}

        user_id = OAuthMetadataExtractor.extract_user_id(
            token_response, userinfo
        )
        if user_id:
            metadata["user_id"] = user_id

        email = OAuthMetadataExtractor.extract_email(token_response, userinfo)
        if email:
            metadata["email"] = email

        instance_url = OAuthMetadataExtractor.extract_instance_url(
            token_response
        )
        if instance_url:
            metadata["instance_url"] = instance_url

        # Include full userinfo if available
        if userinfo:
            metadata["userinfo"] = userinfo

        return metadata

    # =========================================================================
    # PROVIDER-SPECIFIC EXTRACTION METHODS
    # =========================================================================

    @staticmethod
    def extract_salesforce_metadata(
        token_response: Dict[str, Any],
    ) -> Dict[str, Any]:
        """
        Extract Salesforce metadata from OAuth response.

        CRITICAL: instance_url is REQUIRED for API calls!
        Salesforce uses instance-specific URLs (e.g., na1.salesforce.com)

        Args:
            token_response: Salesforce OAuth token response

        Returns:
            Dict with instance_url, organization_id, user_id, is_sandbox

        Raises:
            OAuthMetadataExtractionError: If instance_url is missing
        """
        try:
            # Extract instance URL (CRITICAL!)
            instance_url = token_response.get("instance_url")
            if not instance_url:
                raise OAuthMetadataExtractionError(
                    "salesforce",
                    "Missing instance_url in token response. "
                    "This is required for Salesforce API calls.",
                )

            # Parse identity URL for org and user IDs
            # Format: https://login.salesforce.com/id/00D.../005...
            id_url = token_response.get("id", "")
            parts = id_url.split("/")

            return {
                "instance_url": instance_url,
                "organization_id": parts[-2] if len(parts) >= 2 else None,
                "user_id": parts[-1] if len(parts) >= 1 else None,
                "is_sandbox": "sandbox" in instance_url.lower()
                or "test" in instance_url.lower(),
                "issued_at": token_response.get("issued_at"),
            }

        except OAuthMetadataExtractionError:
            raise
        except Exception as e:
            logger.error(
                "Salesforce metadata extraction failed",
                extra={"error": str(e)},
            )
            raise OAuthMetadataExtractionError(
                "salesforce", f"Failed to extract metadata: {e}"
            )

    @staticmethod
    def extract_atlassian_metadata(
        token_response: Dict[str, Any],
    ) -> Dict[str, Any]:
        """
        Extract Atlassian/Jira metadata from OAuth response.

        Jira Cloud uses a unique accessible resources pattern where
        the token response includes a list of accessible sites.

        Args:
            token_response: Atlassian OAuth token response

        Returns:
            Dict with cloud_id, site_url, account_id, scopes
        """
        try:
            # Jira returns accessible resources
            resources = token_response.get("resources", [])
            first_resource = resources[0] if resources else {}

            return {
                "cloud_id": first_resource.get("id"),
                "site_url": first_resource.get("url"),
                "site_name": first_resource.get("name"),
                "account_id": token_response.get("account_id"),
                "scopes": token_response.get("scope", "").split()
                if token_response.get("scope")
                else [],
            }

        except Exception as e:
            logger.warning(
                "Atlassian metadata extraction failed",
                extra={"error": str(e)},
            )
            # Jira metadata is nice-to-have, not critical
            return {}

    # Alias for jira
    extract_jira_metadata = extract_atlassian_metadata

    @staticmethod
    def extract_microsoft_metadata(
        token_response: Dict[str, Any],
    ) -> Dict[str, Any]:
        """
        Extract Microsoft/Azure AD metadata from OAuth response.

        Microsoft includes user info in the id_token JWT.
        We decode it without verification to extract claims.

        Args:
            token_response: Microsoft OAuth token response

        Returns:
            Dict with tenant_id, user_principal_name, object_id
        """
        try:
            # Microsoft includes tenant info in id_token
            id_token = token_response.get("id_token")

            if id_token:
                try:
                    import jwt

                    # Decode without verification (just for metadata extraction)
                    # The token was already validated by the OAuth flow
                    claims = jwt.decode(
                        id_token, options={"verify_signature": False}
                    )

                    return {
                        "tenant_id": claims.get("tid"),
                        "user_principal_name": claims.get("preferred_username"),
                        "object_id": claims.get("oid"),
                        "name": claims.get("name"),
                        "email": claims.get("email")
                        or claims.get("preferred_username"),
                    }
                except ImportError:
                    logger.warning(
                        "PyJWT not installed, cannot decode Microsoft id_token"
                    )
                    return {}

            return {}

        except Exception as e:
            logger.warning(
                "Microsoft metadata extraction failed",
                extra={"error": str(e)},
            )
            return {}

    # Alias for azure
    extract_azure_metadata = extract_microsoft_metadata

    @staticmethod
    def extract_slack_metadata(
        token_response: Dict[str, Any],
    ) -> Dict[str, Any]:
        """
        Extract Slack metadata from OAuth response.

        Slack has a unique response format with nested team
        and authed_user objects.

        Args:
            token_response: Slack OAuth token response

        Returns:
            Dict with team_id, team_name, user_id, scopes
        """
        try:
            team = token_response.get("team", {})
            authed_user = token_response.get("authed_user", {})

            # Slack uses comma-separated scopes, not space-separated
            scope_str = token_response.get("scope", "")
            scopes = scope_str.split(",") if scope_str else []

            return {
                "team_id": team.get("id"),
                "team_name": team.get("name"),
                "user_id": authed_user.get("id"),
                "enterprise_id": token_response.get("enterprise", {}).get("id"),
                "scopes": scopes,
                "bot_user_id": token_response.get("bot_user_id"),
            }

        except Exception as e:
            logger.warning(
                "Slack metadata extraction failed",
                extra={"error": str(e)},
            )
            return {}

    @staticmethod
    def extract_google_metadata(
        token_response: Dict[str, Any],
        userinfo: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Extract Google metadata from OAuth response.

        Google typically requires a separate userinfo call,
        but may include basic claims in id_token.

        Args:
            token_response: Google OAuth token response
            userinfo: Optional userinfo endpoint response

        Returns:
            Dict with sub, email, name, picture
        """
        try:
            metadata: Dict[str, Any] = {}

            # Try userinfo first
            if userinfo:
                metadata = {
                    "sub": userinfo.get("sub"),
                    "email": userinfo.get("email"),
                    "email_verified": userinfo.get("email_verified"),
                    "name": userinfo.get("name"),
                    "given_name": userinfo.get("given_name"),
                    "family_name": userinfo.get("family_name"),
                    "picture": userinfo.get("picture"),
                    "locale": userinfo.get("locale"),
                    "hd": userinfo.get("hd"),  # Hosted domain for Workspace
                }
                # Remove None values
                return {k: v for k, v in metadata.items() if v is not None}

            # Try id_token if no userinfo
            id_token = token_response.get("id_token")
            if id_token:
                try:
                    import jwt

                    claims = jwt.decode(
                        id_token, options={"verify_signature": False}
                    )
                    return {
                        "sub": claims.get("sub"),
                        "email": claims.get("email"),
                        "email_verified": claims.get("email_verified"),
                        "name": claims.get("name"),
                        "picture": claims.get("picture"),
                        "hd": claims.get("hd"),
                    }
                except ImportError:
                    logger.warning(
                        "PyJWT not installed, cannot decode Google id_token"
                    )

            return metadata

        except Exception as e:
            logger.warning(
                "Google metadata extraction failed",
                extra={"error": str(e)},
            )
            return {}

    @staticmethod
    def extract_github_metadata(
        token_response: Dict[str, Any],
        userinfo: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Extract GitHub metadata from OAuth response.

        GitHub requires a separate user API call for most info.

        Args:
            token_response: GitHub OAuth token response
            userinfo: Optional user API response

        Returns:
            Dict with id, login, email, name
        """
        try:
            if userinfo:
                return {
                    "id": userinfo.get("id"),
                    "login": userinfo.get("login"),
                    "email": userinfo.get("email"),
                    "name": userinfo.get("name"),
                    "avatar_url": userinfo.get("avatar_url"),
                    "company": userinfo.get("company"),
                    "type": userinfo.get("type"),  # User or Organization
                }

            return {}

        except Exception as e:
            logger.warning(
                "GitHub metadata extraction failed",
                extra={"error": str(e)},
            )
            return {}

    # =========================================================================
    # MAIN EXTRACTION METHOD
    # =========================================================================

    @classmethod
    def extract_metadata(
        cls,
        provider_type: str,
        token_response: Dict[str, Any],
        userinfo: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Extract metadata for any supported provider.

        This is the main entry point. It routes to the correct
        provider-specific extractor based on provider_type.

        Args:
            provider_type: Provider name (salesforce, microsoft, slack, etc.)
            token_response: OAuth token response
            userinfo: Optional userinfo endpoint response

        Returns:
            Extracted metadata dict (provider-specific fields)

        Example:
            # Salesforce
            metadata = OAuthMetadataExtractor.extract_metadata(
                "salesforce", token_response
            )
            print(metadata["instance_url"])

            # Microsoft
            metadata = OAuthMetadataExtractor.extract_metadata(
                "microsoft", token_response
            )
            print(metadata["tenant_id"])
        """
        # Map provider names to extractors
        extractors = {
            "salesforce": cls.extract_salesforce_metadata,
            "atlassian": cls.extract_atlassian_metadata,
            "jira": cls.extract_atlassian_metadata,
            "microsoft": cls.extract_microsoft_metadata,
            "azure": cls.extract_microsoft_metadata,
            "azuread": cls.extract_microsoft_metadata,
            "slack": cls.extract_slack_metadata,
            "google": lambda tr: cls.extract_google_metadata(tr, userinfo),
            "github": lambda tr: cls.extract_github_metadata(tr, userinfo),
        }

        provider_lower = provider_type.lower()
        extractor = extractors.get(provider_lower)

        if extractor:
            try:
                return extractor(token_response)
            except OAuthMetadataExtractionError:
                # Re-raise critical errors
                raise
            except Exception as e:
                logger.warning(
                    f"Metadata extraction failed for {provider_type}",
                    extra={"error": str(e)},
                )
                return {}

        # Unknown provider - use generic extraction
        logger.debug(
            f"No specific extractor for provider '{provider_type}', "
            "using generic extraction"
        )
        return cls.build_metadata(token_response, userinfo)

    @classmethod
    def get_supported_providers(cls) -> List[str]:
        """
        Get list of providers with specific extractors.

        Returns:
            List of provider names
        """
        return [
            "salesforce",
            "atlassian",
            "jira",
            "microsoft",
            "azure",
            "slack",
            "google",
            "github",
        ]
