"""
Authentication and token management for GetBiji API.

Handles token refresh and automatic token validation.
"""

import time
from typing import Optional

import requests

from .exceptions import APIError, AuthenticationError, TokenExpiredError
from .models import TokenInfo
from .utils import generate_request_id


class AuthAPI:
    """
    Handles authentication operations for GetBiji API.

    This class manages token storage, validation, and refresh operations.
    """

    # API endpoints
    REFRESH_URL = "https://notes-api.biji.com/account/v2/web/user/auth/refresh"

    def __init__(
        self,
        token: str,
        refresh_token: Optional[str] = None,
        token_expire_at: Optional[int] = None,
        refresh_token_expire_at: Optional[int] = None,
        session: Optional[requests.Session] = None,
    ):
        """
        Initialize authentication manager.

        Args:
            token: Access token (JWT)
            refresh_token: Refresh token (optional but recommended)
            token_expire_at: Access token expiration timestamp (seconds)
            refresh_token_expire_at: Refresh token expiration timestamp (seconds)
            session: Optional requests.Session to use for API calls
        """
        self._token = token
        self._refresh_token = refresh_token or ""
        self._token_expire_at = token_expire_at or 0
        self._refresh_token_expire_at = refresh_token_expire_at or 0

        # Use provided session or create new one
        self._session = session or requests.Session()

        # Default headers
        self._session.headers.update(
            {
                "Authorization": f"Bearer {self._token}",
                "Content-Type": "application/json",
                "Accept": "*/*",
            }
        )

    @property
    def token(self) -> str:
        """Get current access token."""
        return self._token

    @property
    def refresh_token(self) -> str:
        """Get current refresh token."""
        return self._refresh_token

    @property
    def token_expire_at(self) -> int:
        """Get access token expiration timestamp."""
        return self._token_expire_at

    @property
    def refresh_token_expire_at(self) -> int:
        """Get refresh token expiration timestamp."""
        return self._refresh_token_expire_at

    def is_token_expired(self, buffer_seconds: int = 300) -> bool:
        """
        Check if the access token is expired or will expire soon.

        Args:
            buffer_seconds: Buffer time in seconds (default: 5 minutes)

        Returns:
            True if token is expired or will expire within buffer time
        """
        # If expire_at is not set (0), assume it's not expired
        if self._token_expire_at == 0:
            return False
        current_time = int(time.time())
        return current_time >= (self._token_expire_at - buffer_seconds)

    def is_refresh_token_expired(self) -> bool:
        """
        Check if the refresh token is expired.

        Returns:
            True if refresh token is expired
        """
        # If expire_at is not set (0), assume it's not expired
        if self._refresh_token_expire_at == 0:
            return False
        current_time = int(time.time())
        return current_time >= self._refresh_token_expire_at

    def _parse_jwt_expiry(self, token: str) -> int:
        """
        Parse expiration time from JWT token.

        Args:
            token: JWT token string

        Returns:
            Expiration timestamp (seconds since epoch)
        """
        try:
            import base64
            import json

            # JWT is in format: header.payload.signature
            parts = token.split(".")
            if len(parts) >= 2:
                # Decode payload (add padding if needed)
                payload = parts[1]
                padding = 4 - len(payload) % 4
                if padding != 4:
                    payload += "=" * padding
                decoded = base64.b64decode(payload)
                data = json.loads(decoded)
                return data.get("exp", 0)
        except Exception:
            pass
        return 0

    def update_tokens(self, token_info: TokenInfo):
        """
        Update stored tokens with new values.

        Args:
            token_info: New token information
        """
        self._token = token_info.token
        self._refresh_token = token_info.refresh_token
        self._token_expire_at = token_info.token_expire_at
        self._refresh_token_expire_at = token_info.refresh_token_expire_at

        # Update session header with new token
        self._session.headers.update(
            {
                "Authorization": f"Bearer {self._token}",
            }
        )

    def refresh(self) -> TokenInfo:
        """
        Refresh the access token using the refresh token.

        Returns:
            TokenInfo with new tokens

        Raises:
            AuthenticationError: If refresh fails
            TokenExpiredError: If refresh token is expired
            APIError: If API returns an error
        """
        if not self._refresh_token:
            raise AuthenticationError("No refresh token available")

        if self.is_refresh_token_expired():
            raise TokenExpiredError("Refresh token has expired")

        # Prepare request
        headers = {
            "Authorization": f"Bearer {self._token}",
            "Content-Type": "application/json",
            "Xi-Csrf-Token": "",  # May need to extract from previous response
            "X-Request-ID": generate_request_id(),
        }

        payload = {"refresh_token": self._refresh_token}

        try:
            response = self._session.post(
                self.REFRESH_URL,
                json=payload,
                headers=headers,
            )
            response.raise_for_status()

            data = response.json()

            # Check response status
            h = data.get("h", {})
            if h.get("c") != 0:
                error_msg = h.get("e", "Token refresh failed")
                raise APIError(h.get("c", -1), error_msg)

            c = data.get("c", {})
            if not c.get("success"):
                raise AuthenticationError("Token refresh was not successful")

            # Extract token info
            token_data = c.get("token", {})
            token_info = TokenInfo(
                token=token_data.get("token", ""),
                refresh_token=token_data.get("refresh_token", ""),
                token_expire_at=token_data.get("token_expire_at", 0),
                refresh_token_expire_at=token_data.get("refresh_token_expire_at", 0),
                jti=token_data.get("jti", ""),
            )

            # Update stored tokens
            self.update_tokens(token_info)

            return token_info

        except requests.RequestException as e:
            raise AuthenticationError(f"Failed to refresh token: {e}")

    def ensure_token_valid(self, buffer_seconds: int = 300):
        """
        Ensure the access token is valid, refreshing if necessary.

        Args:
            buffer_seconds: Buffer time in seconds before expiration (default: 5 minutes)

        Raises:
            AuthenticationError: If token cannot be refreshed
            TokenExpiredError: If refresh token is expired
        """
        if self.is_token_expired(buffer_seconds):
            self.refresh()

    def get_session(self) -> requests.Session:
        """
        Get the requests session with auth headers.

        Returns:
            Configured requests.Session
        """
        return self._session

    def set_session(self, session: requests.Session):
        """
        Set a new requests session.

        Args:
            session: New requests.Session instance
        """
        self._session = session
        self._session.headers.update(
            {
                "Authorization": f"Bearer {self._token}",
                "Content-Type": "application/json",
            }
        )


class AuthManager:
    """
    High-level authentication manager for use in clients.

    This class wraps AuthAPI and provides convenient methods
    for token management in API clients.
    """

    def __init__(
        self,
        token: str,
        refresh_token: Optional[str] = None,
        token_expire_at: Optional[int] = None,
        refresh_token_expire_at: Optional[int] = None,
    ):
        """
        Initialize auth manager.

        Args:
            token: Access token
            refresh_token: Refresh token
            token_expire_at: Token expiration timestamp
            refresh_token_expire_at: Refresh token expiration timestamp
        """
        self._auth_api = AuthAPI(
            token=token,
            refresh_token=refresh_token,
            token_expire_at=token_expire_at,
            refresh_token_expire_at=refresh_token_expire_at,
        )

    @property
    def auth_api(self) -> AuthAPI:
        """Get underlying AuthAPI instance."""
        return self._auth_api

    @property
    def token(self) -> str:
        """Get current access token."""
        return self._auth_api.token

    @property
    def session(self) -> requests.Session:
        """Get configured requests session."""
        return self._auth_api.get_session()

    def ensure_valid(self, buffer_seconds: int = 300):
        """
        Ensure token is valid, refreshing if necessary.

        Args:
            buffer_seconds: Buffer time in seconds before expiration
        """
        self._auth_api.ensure_token_valid(buffer_seconds)

    def refresh(self) -> TokenInfo:
        """Manually trigger token refresh."""
        return self._auth_api.refresh()
