"""Session-based HTTP client for CATAPA Private API.

This module provides a wrapper around requests.Session that automatically
handles authentication and session management for CATAPA Private API calls.

Authors:
    Handrian Alandi (handrian.alandi@gdplabs.id)

References:
    NONE
"""

from typing import Any

import requests
from catapa_private.auth.catapa_private_auth import CatapaPrivateAuth, CatapaPrivateConfig

from .constant import USER_AGENT_KEY, USER_AGENT_VALUE


class SessionClient:
    """HTTP client with automatic authentication for CATAPA Private API.

    This client automatically handles login and session management,
    making it easy to make authenticated requests to the CATAPA Private API.

    Attributes:
        auth (CatapaPrivateAuth): Authentication instance for session management.
        _headers (dict[str, str] | None): Cached authentication headers.
        _session (requests.Session | None): Cached authenticated session.

    Example:
        client = SessionClient(CatapaPrivateConfig(
            base_url="https://api.catapa.com",
            tenant="your-tenant",
            username="your-username",
            password="your-password"
        ))

        response = client.get("/timemanagement/attendance-statuses")
        data = response.json()
    """

    def __init__(self, config: CatapaPrivateConfig) -> None:
        """Initialize the session client.

        Args:
            config (CatapaPrivateConfig): Configuration for CATAPA Private API authentication.
        """
        self.auth = CatapaPrivateAuth(config)
        self._headers: dict[str, str] | None = None
        self._session: requests.Session | None = None

    def _ensure_authenticated(self) -> None:
        """Ensure we have valid authentication headers and session."""
        if not self._headers or not self._session or not self.auth._is_session_valid():
            self._headers, self._session = self.auth.get_authenticated_session()
            # After authentication, these should never be None
            assert self._headers is not None
            assert self._session is not None

        self._headers[USER_AGENT_KEY] = USER_AGENT_VALUE

    def get(self, url: str, **kwargs: Any) -> requests.Response:
        """Make a GET request to the specified URL.

        Args:
            url (str): URL path (relative to base_url) or full URL.
            **kwargs: Additional arguments to pass to requests.get().

        Returns:
            requests.Response: Response object from the request.

        Raises:
            requests.exceptions.RequestException: If the request fails.
        """
        self._ensure_authenticated()
        full_url = url if url.startswith("http") else f"{self.auth.config.base_url}{url}"
        return self._session.get(full_url, headers=self._headers, **kwargs)

    def post(self, url: str, **kwargs: Any) -> requests.Response:
        """Make a POST request to the specified URL.

        Args:
            url (str): URL path (relative to base_url) or full URL.
            **kwargs: Additional arguments to pass to requests.post().

        Returns:
            requests.Response: Response object from the request.

        Raises:
            requests.exceptions.RequestException: If the request fails.
        """
        self._ensure_authenticated()
        full_url = url if url.startswith("http") else f"{self.auth.config.base_url}{url}"
        return self._session.post(full_url, headers=self._headers, **kwargs)

    def put(self, url: str, **kwargs: Any) -> requests.Response:
        """Make a PUT request to the specified URL.

        Args:
            url (str): URL path (relative to base_url) or full URL.
            **kwargs: Additional arguments to pass to requests.put().

        Returns:
            requests.Response: Response object from the request.

        Raises:
            requests.exceptions.RequestException: If the request fails.
        """
        self._ensure_authenticated()
        full_url = url if url.startswith("http") else f"{self.auth.config.base_url}{url}"
        return self._session.put(full_url, headers=self._headers, **kwargs)

    def delete(self, url: str, **kwargs: Any) -> requests.Response:
        """Make a DELETE request to the specified URL.

        Args:
            url (str): URL path (relative to base_url) or full URL.
            **kwargs: Additional arguments to pass to requests.delete().

        Returns:
            requests.Response: Response object from the request.

        Raises:
            requests.exceptions.RequestException: If the request fails.
        """
        self._ensure_authenticated()
        full_url = url if url.startswith("http") else f"{self.auth.config.base_url}{url}"
        return self._session.delete(full_url, headers=self._headers, **kwargs)

    def patch(self, url: str, **kwargs: Any) -> requests.Response:
        """Make a PATCH request to the specified URL.

        Args:
            url (str): URL path (relative to base_url) or full URL.
            **kwargs: Additional arguments to pass to requests.patch().

        Returns:
            requests.Response: Response object from the request.

        Raises:
            requests.exceptions.RequestException: If the request fails.
        """
        self._ensure_authenticated()
        full_url = url if url.startswith("http") else f"{self.auth.config.base_url}{url}"
        return self._session.patch(full_url, headers=self._headers, **kwargs)

    def refresh_session(self) -> None:
        """Force refresh the authentication session."""
        self.auth.refresh_session()
        self._headers = None
        self._session = None

    def is_session_valid(self) -> bool:
        """Check if the current session is valid.

        Returns:
            bool: True if session is valid, False otherwise.
        """
        return self.auth._is_session_valid()

    def close(self) -> None:
        """Close the session and clean up resources."""
        self.auth.close_session()
        self._headers = None
        self._session = None

    def __enter__(self) -> "SessionClient":
        """Context manager entry.

        Returns:
            SessionClient: The session client instance.
        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Context manager exit - closes the session."""
        self.close()
