from typing import Optional, Dict, Any
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
import httpx
import secrets
from urllib.parse import urlencode
import logging

from ...security.models.security_event import OAuthProvider, OAuthToken
from ..models.user import User
from ...shared.config.logging_config import get_logger

logger = get_logger(__name__)

class OAuthService:
    """Service for handling OAuth 2.0 / OpenID Connect authentication"""
    
    @staticmethod
    def get_authorization_url(db: Session, provider_name: str, redirect_uri: str, state: Optional[str] = None) -> str:
        """Generate OAuth authorization URL"""
        provider = db.query(OAuthProvider).filter(
            OAuthProvider.name == provider_name,
            OAuthProvider.is_active == True
        ).first()
        
        if not provider:
            raise ValueError(f"OAuth provider '{provider_name}' not found or inactive")
        
        if not state:
            state = secrets.token_urlsafe(32)
        
        params = {
            'client_id': provider.client_id,
            'redirect_uri': redirect_uri,
            'response_type': 'code',
            'scope': provider.scopes or 'openid profile email',
            'state': state,
        }
        
        return f"{provider.authorization_url}?{urlencode(params)}"
    
    @staticmethod
    async def exchange_code_for_token(
        db: Session,
        provider_name: str,
        code: str,
        redirect_uri: str
    ) -> Dict[str, Any]:
        """Exchange authorization code for access token"""
        provider = db.query(OAuthProvider).filter(
            OAuthProvider.name == provider_name,
            OAuthProvider.is_active == True
        ).first()
        
        if not provider:
            raise ValueError(f"OAuth provider '{provider_name}' not found or inactive")
        
        async with httpx.AsyncClient() as client:
            response = await client.post(
                provider.token_url,
                data={
                    'grant_type': 'authorization_code',
                    'code': code,
                    'redirect_uri': redirect_uri,
                    'client_id': provider.client_id,
                    'client_secret': provider.client_secret,
                },
                headers={'Accept': 'application/json'}
            )
            
            if response.status_code != 200:
                logger.error(f"OAuth token exchange failed: {response.text}")
                raise ValueError("Failed to exchange authorization code for token")
            
            token_data = response.json()
            return token_data
    
    @staticmethod
    async def get_user_info(
        db: Session,
        provider_name: str,
        access_token: str
    ) -> Dict[str, Any]:
        """Get user information from OAuth provider"""
        provider = db.query(OAuthProvider).filter(
            OAuthProvider.name == provider_name,
            OAuthProvider.is_active == True
        ).first()
        
        if not provider:
            raise ValueError(f"OAuth provider '{provider_name}' not found or inactive")
        
        async with httpx.AsyncClient() as client:
            response = await client.get(
                provider.userinfo_url,
                headers={
                    'Authorization': f'Bearer {access_token}',
                    'Accept': 'application/json'
                }
            )
            
            if response.status_code != 200:
                logger.error(f"Failed to get user info: {response.text}")
                raise ValueError("Failed to get user information from OAuth provider")
            
            return response.json()
    
    @staticmethod
    def save_oauth_token(
        db: Session,
        user_id: int,
        provider_id: int,
        provider_user_id: str,
        access_token: str,
        refresh_token: Optional[str] = None,
        expires_in: Optional[int] = None,
        scopes: Optional[str] = None
    ) -> OAuthToken:
        """Save or update OAuth token for user"""
        expires_at = None
        if expires_in:
            expires_at = datetime.utcnow() + timedelta(seconds=expires_in)
        
        # Check if token already exists
        existing_token = db.query(OAuthToken).filter(
            OAuthToken.user_id == user_id,
            OAuthToken.provider_id == provider_id
        ).first()
        
        if existing_token:
            existing_token.access_token = access_token
            existing_token.refresh_token = refresh_token
            existing_token.expires_at = expires_at
            existing_token.scopes = scopes
            existing_token.updated_at = datetime.utcnow()
            db.commit()
            db.refresh(existing_token)
            return existing_token
        else:
            new_token = OAuthToken(
                user_id=user_id,
                provider_id=provider_id,
                provider_user_id=provider_user_id,
                access_token=access_token,
                refresh_token=refresh_token,
                expires_at=expires_at,
                scopes=scopes
            )
            db.add(new_token)
            db.commit()
            db.refresh(new_token)
            return new_token
    
    @staticmethod
    def find_or_create_user_from_oauth(
        db: Session,
        provider_name: str,
        user_info: Dict[str, Any]
    ) -> User:
        """Find existing user or create new user from OAuth user info"""
        provider = db.query(OAuthProvider).filter(
            OAuthProvider.name == provider_name
        ).first()
        
        if not provider:
            raise ValueError(f"OAuth provider '{provider_name}' not found")
        
        # Try to find user by OAuth token
        provider_user_id = user_info.get('sub') or user_info.get('id')
        oauth_token = db.query(OAuthToken).filter(
            OAuthToken.provider_id == provider.id,
            OAuthToken.provider_user_id == str(provider_user_id)
        ).first()
        
        if oauth_token:
            return oauth_token.user
        
        # Try to find user by email
        email = user_info.get('email')
        if email:
            user = db.query(User).filter(User.email == email.lower()).first()
            if user:
                return user
        
        # Create new user
        from ..models.role import Role
        customer_role = db.query(Role).filter(Role.name == 'customer').first()
        if not customer_role:
            raise ValueError("Customer role not found")
        
        name = user_info.get('name') or user_info.get('given_name', '') + ' ' + user_info.get('family_name', '')
        if not name.strip():
            name = email.split('@')[0] if email else 'User'
        
        new_user = User(
            email=email.lower() if email else f"{provider_user_id}@{provider_name}.oauth",
            full_name=name.strip(),
            role_id=customer_role.id,
            is_active=True,
            email_verified=True  # OAuth providers verify emails
        )
        db.add(new_user)
        db.commit()
        db.refresh(new_user)
        
        return new_user

oauth_service = OAuthService()

