"""
Main GetBiji API client.

This is the entry point for interacting with the GetBiji API.
"""

from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional

import requests

from .auth import AuthManager
from .exceptions import APIError, AuthenticationError, TokenExpiredError
from .models import Note, TokenInfo

if TYPE_CHECKING:
    from .account import AccountAPI
    from .knowledge import KnowledgeAPI
    from .notes import NotesAPI


class GetBijiClient:
    """
    Main client for GetBiji API.

    This client provides access to all GetBiji functionality including:
    - Account management
    - Note creation and management
    - Knowledge base operations

    Example:
        >>> client = GetBijiClient(
        ...     token="your_access_token",
        ...     refresh_token="your_refresh_token"
        ... )
        >>> user = client.account.get_info()
        >>> print(f"Hello, {user.nickname}")
    """

    # API base URLs
    NOTES_API_BASE = "https://get-notes.luojilab.com"
    AUTH_API_BASE = "https://notes-api.biji.com"
    KNOWLEDGE_API_BASE = "https://knowledge-api.trytalks.com"

    def __init__(
        self,
        token: str,
        refresh_token: Optional[str] = None,
        token_expire_at: Optional[int] = None,
        refresh_token_expire_at: Optional[int] = None,
        auto_refresh: bool = True,
        token_refresh_buffer: int = 300,
    ):
        """
        Initialize GetBiji client.

        Args:
            token: Access token (JWT format)
            refresh_token: Refresh token for automatic token renewal
            token_expire_at: Access token expiration timestamp (seconds since epoch)
            refresh_token_expire_at: Refresh token expiration timestamp
            auto_refresh: Whether to automatically refresh tokens (default: True)
            token_refresh_buffer: Buffer time in seconds before expiration to refresh (default: 300)
        """
        self._auto_refresh = auto_refresh
        self._token_refresh_buffer = token_refresh_buffer

        # Initialize auth manager
        self._auth_manager = AuthManager(
            token=token,
            refresh_token=refresh_token,
            token_expire_at=token_expire_at,
            refresh_token_expire_at=refresh_token_expire_at,
        )

        # Lazy-loaded API clients
        self._account_api: Optional["AccountAPI"] = None
        self._notes_api: Optional["NotesAPI"] = None
        self._knowledge_api: Optional["KnowledgeAPI"] = None

    @property
    def account(self) -> "AccountAPI":
        """Get account API for user account operations."""
        if self._account_api is None:
            from .account import AccountAPI

            self._account_api = AccountAPI(self)
        return self._account_api

    @property
    def notes(self) -> "NotesAPI":
        """Get notes API for note operations."""
        if self._notes_api is None:
            from .notes import NotesAPI

            self._notes_api = NotesAPI(self)
        return self._notes_api

    @property
    def knowledge(self) -> "KnowledgeAPI":
        """Get knowledge API for knowledge base operations."""
        if self._knowledge_api is None:
            from .knowledge import KnowledgeAPI

            self._knowledge_api = KnowledgeAPI(self)
        return self._knowledge_api

    @property
    def auth_manager(self) -> AuthManager:
        """Get the auth manager."""
        return self._auth_manager

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

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

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

        Returns:
            TokenInfo with new tokens

        Raises:
            AuthenticationError: If refresh fails
            TokenExpiredError: If refresh token is expired
        """
        return self._auth_manager.refresh()

    def _ensure_token_valid(self):
        """
        Ensure the token is valid, refreshing if necessary.

        This is called automatically before each API request if auto_refresh is enabled.
        """
        if self._auto_refresh:
            self._auth_manager.ensure_valid(self._token_refresh_buffer)

    def _request(
        self,
        method: str,
        url: str,
        *,
        headers: Optional[Dict[str, str]] = None,
        params: Optional[Dict[str, Any]] = None,
        json: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        files: Optional[Dict[str, Any]] = None,
        stream: bool = False,
        retry_on_401: bool = True,
    ) -> requests.Response:
        """
        Make an authenticated API request.

        Args:
            method: HTTP method (GET, POST, PUT, DELETE, etc.)
            url: Full URL or path (if path, will use base URL)
            headers: Additional headers
            params: Query parameters
            json: JSON request body
            data: Raw request body
            files: Files to upload
            stream: Whether to stream the response
            retry_on_401: Whether to retry once on 401 error

        Returns:
            requests.Response object

        Raises:
            AuthenticationError: If authentication fails
            APIError: If API returns an error
        """
        # Ensure token is valid
        self._ensure_token_valid()

        # Prepare headers
        request_headers = {}
        if headers:
            request_headers.update(headers)

        # Make request
        response = self._do_request(
            method=method,
            url=url,
            headers=request_headers,
            params=params,
            json=json,
            data=data,
            files=files,
            stream=stream,
        )

        # Handle 401 - try refreshing token once
        if response.status_code == 401 and retry_on_401 and self._auto_refresh:
            try:
                self.refresh()
            except (AuthenticationError, TokenExpiredError):
                # If refresh fails, return the original 401 response
                return response
            response = self._do_request(
                method=method,
                url=url,
                headers=request_headers,
                params=params,
                json=json,
                data=data,
                files=files,
                stream=stream,
                raise_on_401=True,
            )

        return response

    def _do_request(
        self,
        method: str,
        url: str,
        *,
        headers: Optional[Dict[str, str]] = None,
        params: Optional[Dict[str, Any]] = None,
        json: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        files: Optional[Dict[str, Any]] = None,
        stream: bool = False,
        raise_on_401: bool = False,
    ) -> requests.Response:
        """
        Internal method to perform the actual HTTP request.
        """
        request_headers = dict(headers) if headers else {}

        # Let requests set the Content-Type for multipart/form-data
        if files:
            request_headers.pop("Content-Type", None)

        response = self.session.request(
            method=method,
            url=url,
            headers=request_headers,
            params=params,
            json=json,
            data=data,
            files=files,
            stream=stream,
        )

        # Check for HTTP errors
        if response.status_code == 401:
            if raise_on_401:
                raise AuthenticationError(f"Authentication failed: {response.status_code}")
        elif response.status_code == 403:
            raise AuthenticationError(f"Access forbidden: {response.status_code}")
        elif response.status_code >= 500:
            response.raise_for_status()

        return response

    def _parse_response(self, response: requests.Response) -> Dict[str, Any]:
        """
        Parse JSON response and check for API errors.

        Args:
            response: requests.Response object

        Returns:
            Parsed JSON data

        Raises:
            APIError: If API returns an error code
        """
        try:
            data = response.json()
        except ValueError as e:
            raise APIError(-1, f"Invalid JSON response: {e}")

        # Check API error code in response
        h = data.get("h", {})
        if h.get("c") != 0:
            error_msg = h.get("e", "Unknown error")
            raise APIError(h.get("c", -1), error_msg)

        return data

    def get(
        self,
        url: str,
        *,
        params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """
        Make a GET request.

        Args:
            url: Full URL or endpoint path
            params: Query parameters
            headers: Additional headers

        Returns:
            Parsed JSON response
        """
        response = self._request("GET", url, params=params, headers=headers)
        return self._parse_response(response)

    def post(
        self,
        url: str,
        *,
        json: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        files: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
        stream: bool = False,
    ) -> requests.Response:
        """
        Make a POST request.

        Args:
            url: Full URL or endpoint path
            json: JSON request body
            data: Raw request body
            files: Files to upload
            headers: Additional headers
            stream: Whether to stream the response

        Returns:
            requests.Response object (use _parse_response for JSON responses)
        """
        response = self._request(
            "POST",
            url,
            json=json,
            data=data,
            files=files,
            headers=headers,
            stream=stream,
        )
        return response

    def put(
        self,
        url: str,
        *,
        json: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """
        Make a PUT request.

        Args:
            url: Full URL or endpoint path
            json: JSON request body
            data: Raw request body
            headers: Additional headers

        Returns:
            Parsed JSON response
        """
        response = self._request("PUT", url, json=json, data=data, headers=headers)
        return self._parse_response(response)

    def delete(
        self,
        url: str,
        *,
        params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """
        Make a DELETE request.

        Args:
            url: Full URL or endpoint path
            params: Query parameters
            headers: Additional headers

        Returns:
            Parsed JSON response
        """
        response = self._request("DELETE", url, params=params, headers=headers)
        return self._parse_response(response)

    def close(self):
        """Close the client and release resources."""
        if self._auth_manager and self._auth_manager.session:
            self._auth_manager.session.close()

    def __enter__(self):
        """Context manager entry."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit."""
        self.close()
