"""
Typed response objects for pygeai-gam.

This module provides strongly-typed dataclasses for GAM OAuth responses,
replacing Dict[str, Any] returns with structured objects for better IDE
support and type safety.
"""

from dataclasses import dataclass, field
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta


@dataclass
class GAMUserInfo:
    """
    User information returned from GAM.
    
    This dataclass represents the user information obtained during
    OAuth authentication or from the user info endpoint.
    
    Attributes:
        guid: Unique user identifier (GUID).
        username: User's username.
        email: User's email address (optional).
        roles: List of user's roles (default: empty list).
        raw_data: Original response data for any additional fields.
    
    Example:
        >>> user_info = GAMUserInfo.from_dict({
        ...     "guid": "123e4567-e89b-12d3-a456-426614174000",
        ...     "username": "john.doe",
        ...     "email": "john.doe@example.com",
        ...     "roles": ["admin", "user"]
        ... })
        >>> print(user_info.username)
        john.doe
    """
    
    guid: str
    username: str
    email: Optional[str] = None
    roles: List[str] = field(default_factory=list)
    raw_data: Dict[str, Any] = field(default_factory=dict)
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "GAMUserInfo":
        """
        Create GAMUserInfo from API response dictionary.
        
        Args:
            data: Dictionary containing user information from GAM API.
        
        Returns:
            GAMUserInfo instance with parsed data.
        
        Example:
            >>> data = {"guid": "abc123", "username": "jane", "email": "jane@example.com"}
            >>> user = GAMUserInfo.from_dict(data)
            >>> user.username
            'jane'
        """
        return cls(
            guid=data.get("guid", ""),
            username=data.get("username", ""),
            email=data.get("email"),
            roles=data.get("roles", []),
            raw_data=data,
        )


@dataclass
class GAMTokenResponse:
    """
    Token response from GAM OAuth flows.
    
    This typed response replaces the previous Dict[str, Any] returns,
    providing better IDE support and type safety for OAuth token responses.
    
    Attributes:
        access_token: The OAuth access token.
        token_type: Token type, typically "Bearer" (default: "Bearer").
        expires_in: Token lifetime in seconds (optional).
        refresh_token: Token for obtaining new access tokens (optional).
        scope: Granted OAuth scopes (optional).
        user_info: User information if fetch_user_info=True (optional).
        raw_data: Original response data for any additional fields.
    
    Example:
        >>> token_response = GAMTokenResponse.from_dict({
        ...     "access_token": "eyJhbGci...",
        ...     "token_type": "Bearer",
        ...     "expires_in": 3600,
        ...     "refresh_token": "eyJhbGci...",
        ...     "scope": "gam_user_data"
        ... })
        >>> print(token_response.access_token[:10])
        eyJhbGci..
        >>> if not token_response.is_expired():
        ...     print("Token is still valid")
        Token is still valid
    """
    
    access_token: str
    token_type: str = "Bearer"
    expires_in: Optional[int] = None
    refresh_token: Optional[str] = None
    scope: Optional[str] = None
    user_info: Optional[GAMUserInfo] = None
    raw_data: Dict[str, Any] = field(default_factory=dict)
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any], user_info: Optional[Dict] = None) -> "GAMTokenResponse":
        """
        Create GAMTokenResponse from API response dictionary.
        
        Args:
            data: Dictionary containing token response from GAM API.
            user_info: Optional separate user info dictionary. If not provided,
                      will look for "user_info" key in data.
        
        Returns:
            GAMTokenResponse instance with parsed data.
        
        Example:
            >>> data = {
            ...     "access_token": "abc123",
            ...     "token_type": "Bearer",
            ...     "expires_in": 3600
            ... }
            >>> token = GAMTokenResponse.from_dict(data)
            >>> token.token_type
            'Bearer'
        """
        gam_user_info = None
        if user_info:
            gam_user_info = GAMUserInfo.from_dict(user_info)
        elif "user_info" in data:
            gam_user_info = GAMUserInfo.from_dict(data["user_info"])
        
        return cls(
            access_token=data["access_token"],
            token_type=data.get("token_type", "Bearer"),
            expires_in=data.get("expires_in"),
            refresh_token=data.get("refresh_token"),
            scope=data.get("scope"),
            user_info=gam_user_info,
            raw_data=data,
        )
    
    def get_expiry_time(self) -> Optional[datetime]:
        """
        Calculate absolute expiry time from now.
        
        Returns:
            Datetime object representing when the token expires,
            or None if expires_in is not set.
        
        Example:
            >>> from datetime import datetime, timedelta
            >>> token = GAMTokenResponse(access_token="abc", expires_in=3600)
            >>> expiry = token.get_expiry_time()
            >>> expiry > datetime.now()
            True
        """
        if self.expires_in is None:
            return None
        return datetime.now() + timedelta(seconds=self.expires_in)
    
    def is_expired(self, buffer_seconds: int = 60) -> bool:
        """
        Check if token is expired or about to expire.
        
        Args:
            buffer_seconds: Safety buffer in seconds. Token is considered
                          expired if it expires within this window.
                          Default: 60 seconds.
        
        Returns:
            True if token is expired or will expire within buffer_seconds,
            False if token is still valid.
        
        Example:
            >>> token = GAMTokenResponse(access_token="abc", expires_in=3600)
            >>> token.is_expired()
            False
            >>> token.is_expired(buffer_seconds=4000)
            True
        """
        expiry = self.get_expiry_time()
        if expiry is None:
            return True
        return datetime.now() + timedelta(seconds=buffer_seconds) >= expiry
    
    def to_dict(self) -> Dict[str, Any]:
        """
        Convert back to dictionary format for backward compatibility.
        
        Returns:
            Dictionary representation of the token response.
        
        Example:
            >>> token = GAMTokenResponse(access_token="abc", expires_in=3600)
            >>> data = token.to_dict()
            >>> data["access_token"]
            'abc'
            >>> data["expires_in"]
            3600
        """
        result = {
            "access_token": self.access_token,
            "token_type": self.token_type,
        }
        if self.expires_in is not None:
            result["expires_in"] = self.expires_in
        if self.refresh_token:
            result["refresh_token"] = self.refresh_token
        if self.scope:
            result["scope"] = self.scope
        if self.user_info:
            result["user_info"] = self.user_info.raw_data
        return result
