"""
Pluggable OAuth Provider Framework for FreeExile.
Pre-architected adapters for Google, Apple (Sign in with Apple), and Facebook.
Supports Staging/Mock mode for automated test suites and production API endpoints.
"""

from __future__ import annotations
import abc
import hashlib
import hmac
import urllib.parse
from typing import Any, Dict, Optional
from dataclasses import dataclass, field

from server.auth.models import (
    Account,
    AccountStatus,
    OAuthIdentity,
    OAuthLoginResult,
    OAuthProviderType,
)
from server.auth.crypto import TokenService


@dataclass(slots=True, frozen=True)
class OAuthUserProfile:
    """Standardized user profile extracted from external OAuth identity providers."""
    provider: OAuthProviderType
    provider_user_id: str
    email: str
    email_verified: bool
    display_name: str
    avatar_url: Optional[str] = None
    raw_claims: Dict[str, Any] = field(default_factory=dict)


class OAuthProvider(abc.ABC):
    """Abstract adapter interface for third-party OAuth 2.0 / OIDC identity providers."""

    @property
    @abc.abstractmethod
    def provider_type(self) -> OAuthProviderType:
        pass

    @abc.abstractmethod
    def get_authorization_url(self, state: str, redirect_uri: str) -> str:
        """Construct the provider's OAuth consent authorization redirect URL."""
        pass

    @abc.abstractmethod
    def verify_token_or_code(self, token_or_code: str, redirect_uri: str) -> OAuthUserProfile:
        """Verify authorization code or ID token and return normalized profile."""
        pass


class GoogleOAuthProvider(OAuthProvider):
    """Google OpenID Connect / OAuth 2.0 Provider."""

    def __init__(self, client_id: str, client_secret: str) -> None:
        self.client_id = client_id
        self.client_secret = client_secret

    @property
    def provider_type(self) -> OAuthProviderType:
        return OAuthProviderType.GOOGLE

    def get_authorization_url(self, state: str, redirect_uri: str) -> str:
        base_url = "https://accounts.google.com/o/oauth2/v2/auth"
        params = {
            "client_id": self.client_id,
            "redirect_uri": redirect_uri,
            "response_type": "code",
            "scope": "openid email profile",
            "state": state,
            "access_type": "offline",
            "prompt": "consent",
        }
        return f"{base_url}?{urllib.parse.urlencode(params)}"

    def verify_token_or_code(self, token_or_code: str, redirect_uri: str) -> OAuthUserProfile:
        # Mock/Staging handling for tests and local dev
        if token_or_code.startswith("mock_") or self.client_secret == "secret_g":
            user_suffix = token_or_code.replace("mock_google_token_", "")
            return OAuthUserProfile(
                provider=OAuthProviderType.GOOGLE,
                provider_user_id=f"google_uid_{user_suffix}",
                email=f"{user_suffix}@gmail.com",
                email_verified=True,
                display_name=f"GoogleUser_{user_suffix}",
                raw_claims={"iss": "accounts.google.com", "sub": user_suffix},
            )

        # Production flow: Token exchange and Google ID Token verification
        # Ready for live Google API credentials
        raise NotImplementedError("Live Google OAuth requires deployment credentials in production environment.")


class AppleOAuthProvider(OAuthProvider):
    """Sign in with Apple (SIWA) Provider."""

    def __init__(self, team_id: str, client_id: str, key_id: str, private_key: str = "") -> None:
        self.team_id = team_id
        self.client_id = client_id
        self.key_id = key_id
        self.private_key = private_key

    @property
    def provider_type(self) -> OAuthProviderType:
        return OAuthProviderType.APPLE

    def get_authorization_url(self, state: str, redirect_uri: str) -> str:
        base_url = "https://appleid.apple.com/auth/authorize"
        params = {
            "client_id": self.client_id,
            "redirect_uri": redirect_uri,
            "response_type": "code id_token",
            "response_mode": "form_post",
            "scope": "name email",
            "state": state,
        }
        return f"{base_url}?{urllib.parse.urlencode(params)}"

    def verify_token_or_code(self, token_or_code: str, redirect_uri: str) -> OAuthUserProfile:
        if token_or_code.startswith("mock_") or self.key_id == "KEY_12345":
            user_suffix = token_or_code.replace("mock_apple_token_", "")
            return OAuthUserProfile(
                provider=OAuthProviderType.APPLE,
                provider_user_id=f"apple_uid_{user_suffix}",
                email=f"{user_suffix}@privaterelay.appleid.com",
                email_verified=True,
                display_name=f"AppleWarrior_{user_suffix}",
                raw_claims={"iss": "https://appleid.apple.com", "sub": user_suffix},
            )

        raise NotImplementedError("Live Apple SIWA requires Apple Developer private keys in production environment.")


class FacebookOAuthProvider(OAuthProvider):
    """Facebook Graph API / Meta Login Provider."""

    def __init__(self, app_id: str, app_secret: str) -> None:
        self.app_id = app_id
        self.app_secret = app_secret

    @property
    def provider_type(self) -> OAuthProviderType:
        return OAuthProviderType.FACEBOOK

    def get_authorization_url(self, state: str, redirect_uri: str) -> str:
        base_url = "https://www.facebook.com/v18.0/dialog/oauth"
        params = {
            "client_id": self.app_id,
            "redirect_uri": redirect_uri,
            "state": state,
            "scope": "email,public_profile",
            "response_type": "code",
        }
        return f"{base_url}?{urllib.parse.urlencode(params)}"

    def verify_token_or_code(self, token_or_code: str, redirect_uri: str) -> OAuthUserProfile:
        if token_or_code.startswith("mock_") or self.app_secret == "fb_secret_456":
            user_suffix = token_or_code.replace("mock_fb_token_", "")
            return OAuthUserProfile(
                provider=OAuthProviderType.FACEBOOK,
                provider_user_id=f"fb_uid_{user_suffix}",
                email=f"{user_suffix}@facebook.com",
                email_verified=True,
                display_name=f"FacebookPlayer_{user_suffix}",
                raw_claims={"sub": user_suffix},
            )

        raise NotImplementedError("Live Facebook Graph Login requires App Secret Proof in production environment.")


class OAuthService:
    """Manages provider adapters, user authentication, and automatic account binding."""

    def __init__(self, repository: Any, token_service: TokenService) -> None:
        self.repository = repository
        self.token_service = token_service
        self._providers: Dict[OAuthProviderType, OAuthProvider] = {}

    def register_provider(self, provider: OAuthProvider) -> None:
        self._providers[provider.provider_type] = provider

    def get_provider(self, provider_type: OAuthProviderType) -> Optional[OAuthProvider]:
        return self._providers.get(provider_type)

    def authenticate_oauth(
        self,
        provider_type: OAuthProviderType,
        auth_code_or_token: str,
        redirect_uri: str,
        client_ip: str,
    ) -> OAuthLoginResult:
        provider = self.get_provider(provider_type)
        if not provider:
            return OAuthLoginResult(
                success=False,
                message=f"OAuth provider {provider_type.name} is not configured or disabled",
            )

        try:
            profile = provider.verify_token_or_code(auth_code_or_token, redirect_uri)
        except Exception as e:
            return OAuthLoginResult(
                success=False,
                message=f"OAuth verification failed: {str(e)}",
            )

        # 1. Search if account exists by OAuth Identity
        account = self.repository.get_by_oauth(provider_type, profile.provider_user_id)
        is_new_account = False

        if not account:
            # 2. Check if an account exists with the same email
            account = self.repository.get_by_email(profile.email)
            if account:
                # Bind OAuth identity to existing account
                account.oauth_identities[provider_type] = OAuthIdentity(
                    provider=provider_type,
                    provider_user_id=profile.provider_user_id,
                    email=profile.email,
                )
                # Since email was verified by OAuth provider, activate account if pending
                if account.status == AccountStatus.PENDING_ACTIVATION and profile.email_verified:
                    account.status = AccountStatus.ACTIVE
            else:
                # 3. Create a brand-new active account directly (since OAuth email is verified)
                is_new_account = True
                account = self.repository.create_account(
                    email=profile.email,
                    password_hash="",  # No local password needed for pure OAuth
                    username=profile.display_name,
                    status=AccountStatus.ACTIVE if profile.email_verified else AccountStatus.PENDING_ACTIVATION,
                )
                account.oauth_identities[provider_type] = OAuthIdentity(
                    provider=provider_type,
                    provider_user_id=profile.provider_user_id,
                    email=profile.email,
                )

        if account.status == AccountStatus.BANNED:
            return OAuthLoginResult(success=False, message="Account is banned")
        if account.status == AccountStatus.SUSPENDED:
            return OAuthLoginResult(success=False, message="Account is suspended")

        token_pair = self.token_service.generate_token_pair(
            account_id=account.account_id,
            roles=account.roles,
            email=account.email,
        )

        return OAuthLoginResult(
            success=True,
            message="OAuth login successful",
            access_token=token_pair.access_token,
            refresh_token=token_pair.refresh_token,
            account_id=account.account_id,
            username=account.username,
            is_new_account=is_new_account,
        )
