"""OAuth2 authentication for Heiman Connect."""

import asyncio
import logging
from datetime import datetime, timedelta
from typing import Any, Optional
from urllib.parse import urlencode

import aiohttp

from .exceptions import HeimanAuthError, HeimanConnectionError, HeimanTokenExpiredError
from .models import AuthInfo

_LOGGER = logging.getLogger(__name__)

# OAuth2 constants
OAUTH2_CLIENT_ID = "app"
OAUTH2_STATE_KEY = "heiman_oauth_state"


class HeimanOauthClient:
    """Heiman OAuth2 client for handling authentication flow."""

    def __init__(
        self,
        client_id: str = OAUTH2_CLIENT_ID,
        redirect_url: Optional[str] = None,
        cloud_server: Optional[str] = None,
        region_config: Optional[dict[str, Any]] = None,
        loop: Optional[asyncio.AbstractEventLoop] = None,
    ) -> None:
        """Initialize OAuth client.
        
        Args:
            client_id: OAuth2 client ID
            redirect_url: Redirect URL for OAuth callback
            cloud_server: Cloud server URL
            region_config: Region-specific configuration
            loop: Event loop (deprecated, using asyncio internally)
        """
        self.client_id = client_id
        self.redirect_url = redirect_url or ""
        self.cloud_server = cloud_server
        self.region_config = region_config or {}
        self._session: Optional[aiohttp.ClientSession] = None

    @property
    def api_url(self) -> str:
        """Get API base URL based on region."""
        if self.cloud_server:
            return self.cloud_server
        
        # Default to China region
        return self.region_config.get("api_base_url", "https://api.heiman.cn")

    def gen_auth_url(self) -> str:
        """Generate OAuth2 authorization URL.
        
        Returns:
            Authorization URL string
        """
        params = {
            "client_id": self.client_id,
            "redirect_uri": self.redirect_url,
            "response_type": "code",
            "state": OAUTH2_STATE_KEY,
        }
        
        auth_url = f"{self.api_url}/oauth/authorize"
        return f"{auth_url}?{urlencode(params)}"

    async def get_access_token_async(self, code: str) -> AuthInfo:
        """Exchange authorization code for access token.
        
        Args:
            code: Authorization code from OAuth callback
            
        Returns:
            AuthInfo object with token information
            
        Raises:
            HeimanAuthError: If token exchange fails
        """
        url = f"{self.api_url}/oauth/token"
        
        data = {
            "client_id": self.client_id,
            "code": code,
            "grant_type": "authorization_code",
            "redirect_uri": self.redirect_url,
        }

        try:
            async with aiohttp.ClientSession() as session:
                async with session.post(url, json=data) as response:
                    result = await response.json()
                    
                    if response.status != 200:
                        raise HeimanAuthError(
                            message=f"Token exchange failed: {result.get('message', 'Unknown error')}",
                            error_code=result.get("code"),
                            raw_response=result,
                        )
                    
                    auth_info_data = result.get("data", {})
                    
                    # Parse token information
                    expires_in = auth_info_data.get("expires_in", 0)
                    expires_at = None
                    if expires_in > 0:
                        expires_at = datetime.now() + timedelta(seconds=expires_in)
                    
                    auth_info = AuthInfo(
                        access_token=auth_info_data.get("access_token", ""),
                        refresh_token=auth_info_data.get("refresh_token", ""),
                        token_type=auth_info_data.get("token_type", "Bearer"),
                        expires_in=expires_in,
                        expires_at=expires_at,
                        user_id=auth_info_data.get("user_id"),
                        email=auth_info_data.get("email"),
                    )
                    
                    _LOGGER.debug("Successfully obtained access token")
                    return auth_info
                    
        except aiohttp.ClientError as err:
            raise HeimanConnectionError(
                message=f"Network error during token exchange: {str(err)}",
            ) from err
        except Exception as err:
            raise HeimanAuthError(
                message=f"Unexpected error during token exchange: {str(err)}",
            ) from err

    async def refresh_access_token_async(self, refresh_token: str) -> AuthInfo:
        """Refresh access token using refresh token.
        
        Args:
            refresh_token: Current refresh token
            
        Returns:
            New AuthInfo object with refreshed tokens
            
        Raises:
            HeimanTokenExpiredError: If refresh token is expired
        """
        url = f"{self.api_url}/oauth/token"
        
        data = {
            "client_id": self.client_id,
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
        }

        try:
            async with aiohttp.ClientSession() as session:
                async with session.post(url, json=data) as response:
                    result = await response.json()
                    
                    if response.status != 200:
                        error_msg = result.get("message", "Unknown error")
                        if "refresh" in error_msg.lower() or "expired" in error_msg.lower():
                            raise HeimanTokenExpiredError(
                                message="Refresh token expired, please re-authenticate",
                                error_code=result.get("code"),
                                raw_response=result,
                            )
                        
                        raise HeimanAuthError(
                            message=f"Token refresh failed: {error_msg}",
                            error_code=result.get("code"),
                            raw_response=result,
                        )
                    
                    auth_info_data = result.get("data", {})
                    
                    # Parse token information
                    expires_in = auth_info_data.get("expires_in", 0)
                    expires_at = None
                    if expires_in > 0:
                        expires_at = datetime.now() + timedelta(seconds=expires_in)
                    
                    auth_info = AuthInfo(
                        access_token=auth_info_data.get("access_token", ""),
                        refresh_token=auth_info_data.get("refresh_token", ""),
                        token_type=auth_info_data.get("token_type", "Bearer"),
                        expires_in=expires_in,
                        expires_at=expires_at,
                        user_id=auth_info_data.get("user_id"),
                        email=auth_info_data.get("email"),
                    )
                    
                    _LOGGER.debug("Successfully refreshed access token")
                    return auth_info
                    
        except aiohttp.ClientError as err:
            raise HeimanConnectionError(
                message=f"Network error during token refresh: {str(err)}",
            ) from err
        except Exception as err:
            raise HeimanAuthError(
                message=f"Unexpected error during token refresh: {str(err)}",
            ) from err

    async def close(self) -> None:
        """Close the client session."""
        if self._session and not self._session.closed:
            await self._session.close()


class HeimanAuth:
    """Heiman authentication manager.
    
    This class manages the authentication state and automatically handles
    token refresh when needed.
    """

    def __init__(
        self,
        oauth_client: HeimanOauthClient,
        initial_auth_info: Optional[AuthInfo] = None,
    ) -> None:
        """Initialize authentication manager.
        
        Args:
            oauth_client: OAuth2 client instance
            initial_auth_info: Initial authentication information
        """
        self.oauth_client = oauth_client
        self._auth_info = initial_auth_info
        self._lock = asyncio.Lock()

    @property
    def auth_info(self) -> Optional[AuthInfo]:
        """Get current authentication info."""
        return self._auth_info

    @property
    def access_token(self) -> Optional[str]:
        """Get current access token."""
        return self._auth_info.access_token if self._auth_info else None

    @property
    def refresh_token(self) -> Optional[str]:
        """Get current refresh token."""
        return self._auth_info.refresh_token if self._auth_info else None

    @property
    def user_id(self) -> Optional[str]:
        """Get current user ID."""
        return self._auth_info.user_id if self._auth_info else None

    @property
    def is_authenticated(self) -> bool:
        """Check if currently authenticated."""
        return self._auth_info is not None and bool(self._auth_info.access_token)

    async def ensure_valid_token(self) -> str:
        """Ensure we have a valid access token.
        
        Returns:
            Valid access token string
            
        Raises:
            HeimanAuthError: If authentication fails
        """
        async with self._lock:
            if not self._auth_info:
                raise HeimanAuthError(message="Not authenticated")
            
            # Check if token needs refresh (with 60 second buffer)
            if not self._auth_info.is_token_valid(buffer_seconds=60):
                _LOGGER.debug("Access token expiring soon, refreshing...")
                await self._refresh_token()
            
            return self._auth_info.access_token

    async def _refresh_token(self) -> None:
        """Refresh the access token.
        
        Raises:
            HeimanTokenExpiredError: If refresh token is expired
        """
        if not self._auth_info or not self._auth_info.refresh_token:
            raise HeimanTokenExpiredError(message="No refresh token available")
        
        _LOGGER.info("Refreshing access token")
        
        try:
            new_auth_info = await self.oauth_client.refresh_access_token_async(
                refresh_token=self._auth_info.refresh_token
            )
            self._auth_info = new_auth_info
            _LOGGER.info("Successfully refreshed access token")
            
        except HeimanTokenExpiredError:
            _LOGGER.warning("Refresh token expired, user needs to re-authenticate")
            raise
        except Exception as err:
            _LOGGER.error(f"Token refresh failed: {err}")
            raise

    def update_auth_info(self, auth_info: AuthInfo) -> None:
        """Update authentication info.
        
        Args:
            auth_info: New authentication information
        """
        self._auth_info = auth_info
        _LOGGER.debug("Authentication info updated")
