"""CATAPA Private Authentication Module.

This module handles session-based authentication for the CATAPA Private API,
supporting login with username/password and cookie-based session management.

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

References:
    NONE
"""

from dataclasses import dataclass

import requests
from catapa_private.auth.constant import DEFAULT_TIMEOUT


class CatapaPrivateAuthError(Exception):
    """Exception raised for CATAPA Private authentication errors.

    This exception is raised when authentication fails, such as when
    the login request fails or returns an error response.
    """

    pass


@dataclass
class CatapaPrivateConfig:
    """Configuration for CATAPA Private API authentication.

    Attributes:
        base_url (str): Base URL for the CATAPA API.
        tenant (str): CATAPA tenant identifier.
        username (str): Username for authentication.
        password (str): Password for authentication.
        timeout (int, optional): Timeout for HTTP requests in seconds. Defaults to 30.
    """

    base_url: str
    tenant: str
    username: str
    password: str
    timeout: int = DEFAULT_TIMEOUT


@dataclass
class SessionInfo:
    """Internal session storage with authentication headers.

    Attributes:
        headers (dict[str, str]): Authentication headers including Tenant, Cookie, and XSRF-TOKEN.
        session (requests.Session): Authenticated requests session.
    """

    headers: dict[str, str]
    session: requests.Session


class CatapaPrivateAuth:
    """Handles session-based authentication for CATAPA Private API.

    Features:
    - Automatic login with username/password
    - Session cookie management (SESSION-{tenant} and XSRF-TOKEN)
    - Automatic session refresh when needed

    Attributes:
        config (CatapaPrivateConfig): Configuration for CATAPA Private API authentication.
        _session_info (SessionInfo | None): Internal session storage with authentication headers.

    Example:
        auth = CatapaPrivateAuth(CatapaPrivateConfig(
            tenant="your-tenant",
            username="your-username",
            password="your-password"
        ))

        headers, session = auth.get_authenticated_session()
    """

    def __init__(self, config: CatapaPrivateConfig) -> None:
        """Initialize the CATAPA Private authentication instance.

        Args:
            config (CatapaPrivateConfig): Configuration for CATAPA Private API authentication.
        """
        self.config = config
        self._session_info: SessionInfo | None = None

    def get_authenticated_session(self) -> tuple[dict[str, str], requests.Session]:
        """Get authenticated session, logging in if necessary.

        Returns:
            tuple[dict[str, str], requests.Session]: Tuple containing (headers dict, authenticated session).

        Raises:
            CatapaPrivateAuthError: If authentication fails.
        """
        if self._session_info and self._is_session_valid():
            return self._session_info.headers, self._session_info.session

        return self._login()

    def refresh_session(self) -> None:
        """Force refresh the authentication session."""
        if self._session_info:
            self._session_info.session.close()
        self._session_info = None
        self.get_authenticated_session()

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

        Returns:
            bool: True if session is valid, False otherwise.
        """
        if not self._session_info:
            return False

        if not self._session_info.session:
            return False

        return True

    def _login(self) -> tuple[dict[str, str], requests.Session]:
        """Login to CATAPA Private API and get authentication headers.

        Returns:
            tuple[dict[str, str], requests.Session]: Tuple containing (headers dict, authenticated session).

        Raises:
            CatapaPrivateAuthError: If authentication fails.
        """
        login_url = f"{self.config.base_url}/auth/login"
        login_headers = {
            "Tenant": self.config.tenant,
            "X-XSRF-TOKEN": "<random>",
            "Cookie": "XSRF-TOKEN=<random>",
            "Content-Type": "application/x-www-form-urlencoded",
        }
        data = {"username": self.config.username, "password": self.config.password}

        session = requests.Session()
        try:
            response = session.post(login_url, headers=login_headers, data=data, timeout=self.config.timeout)
            response.raise_for_status()

            cookies = response.cookies
            session_cookie = cookies.get(f"SESSION-{self.config.tenant}")
            xsrf_token = cookies.get("XSRF-TOKEN")

            if not session_cookie or not xsrf_token:
                raise CatapaPrivateAuthError("Failed to obtain session cookies from login response")

            headers = {
                "Tenant": self.config.tenant,
                "Cookie": f"SESSION-{self.config.tenant}={session_cookie};XSRF-TOKEN={xsrf_token}",
                "Content-Type": "application/json",
                "X-XSRF-TOKEN": xsrf_token,
            }

            self._session_info = SessionInfo(headers=headers, session=session)

            return headers, session

        except requests.exceptions.RequestException as e:
            session.close()
            raise CatapaPrivateAuthError(f"Failed to authenticate: {str(e)}") from e

    def close_session(self) -> None:
        """Close the current session and clear session info."""
        if self._session_info:
            self._session_info.session.close()
            self._session_info = None

    def __enter__(self) -> tuple[dict[str, str], requests.Session]:
        """Context manager entry.

        Returns:
            tuple[dict[str, str], requests.Session]: Authenticated headers and session.
        """
        return self.get_authenticated_session()

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