
"""
cellpointapi

This file was automatically generated by APIMATIC v3.0 (
 https://www.apimatic.io ).
"""

import os

from apimatic_core.authentication.header_auth import (
    HeaderAuth,
)
from apimatic_core.utilities.auth_helper import (
    AuthHelper,
)

from cellpointapi.api_helper import APIHelper
from cellpointapi.apis.oauth_authorization_api import (
    OauthAuthorizationApi,
)
from cellpointapi.exceptions.api_exception import (
    ApiException,
)
from cellpointapi.models.oauth_token import (
    OauthToken,
)


class CpdIssuedJwt(HeaderAuth):
    """
    An authentication handler that applies `ClientCredentialsAuth` to
    outgoing requests. It constructs the required credential values and integrates
    with the core authentication framework.
    """

    @property
    def error_message(self):
        """Return reason about the authentication failure."""
        return "CpdIssuedJwt: OAuthToken is undefined or expired."

    def __init__(self, auth_credentials_model, config):
        """
        Initialize the authentication handler with credential data.

        Args:
            auth_credentials_model: The credentials object used to generate
                the authorization header.
            config: The configuration instance.

        """
        self._oauth_client_id = auth_credentials_model.oauth_client_id \
            if auth_credentials_model is not None else None
        self._oauth_client_secret = auth_credentials_model.oauth_client_secret \
            if auth_credentials_model is not None else None
        if auth_credentials_model is not None \
                and isinstance(auth_credentials_model.oauth_token, OauthToken):
            self._oauth_token = OauthToken.from_dictionary(
                APIHelper.to_dictionary(auth_credentials_model.oauth_token))
        else:
            self._oauth_token = auth_credentials_model.oauth_token \
                if auth_credentials_model is not None else None
        if auth_credentials_model is not None \
                and isinstance(auth_credentials_model.oauth_scopes, list):
            self._oauth_scopes = auth_credentials_model.oauth_scopes
        else:
            self._oauth_scopes = None
        self._oauth_clock_skew = auth_credentials_model.oauth_clock_skew \
            if auth_credentials_model is not None else None
        self._oauth_token_provider = auth_credentials_model.oauth_token_provider \
            if auth_credentials_model is not None else None
        self._oauth_on_token_update = auth_credentials_model.oauth_on_token_update \
            if auth_credentials_model is not None else None
        self._o_auth_api = OauthAuthorizationApi(config)
        super().__init__(auth_params={})

    def is_valid(self):
        """
        Validate credentials for authentication.

        Returns:
            bool: True if the credentials are valid, False otherwise.

        """
        self._oauth_token = self._get_token_from_provider()
        return (self._oauth_token and isinstance(self._oauth_token, OauthToken)
                and not self.is_token_expired(self._oauth_token))

    def build_basic_auth_header(self):
        """
        Build the basic auth header for endpoints in the
        OAuth Authorization Controller.

        Returns:
            str: The value of the Authentication header.

        """
        encoded = AuthHelper.get_base64_encoded_value(
            self._oauth_client_id,
            self._oauth_client_secret,
        )
        return f"Basic {encoded}"

    def fetch_token(self, additional_params=None):
        """
        Execute call to fetch the OAuth token.

        Args:
            additional_params (dict):  Any additional form parameters.

        Returns:
            OAuthToken: The OAuth token.

        """
        token = self._o_auth_api.request_token_cpd_issued_jwt(
            self.build_basic_auth_header(),
            " ".join(self._oauth_scopes) if self._oauth_scopes else None,
            _optional_form_parameters=additional_params,
        ).body
        if hasattr(token, "expires_in"):
            current_utc_timestamp = AuthHelper.get_current_utc_timestamp()
            token.expiry = AuthHelper.get_token_expiry(
                current_utc_timestamp,
                token.expires_in,
            )
        return token

    def is_token_expired(self, oauth_token=None):
        """
        Check if OAuth token has expired.

        Args:
            oauth_token (OauthToken): The OAuth token whose expiry is to be checked.

        Returns:
            bool: True if OAuth token has expired, False otherwise.

        """
        if oauth_token is None:
            return (hasattr(self._oauth_token, "expiry")
                    and AuthHelper.is_token_expired(
                        self._oauth_token.expiry, self._oauth_clock_skew))

        return (hasattr(oauth_token, "expiry")
                and AuthHelper.is_token_expired(
                    oauth_token.expiry, self._oauth_clock_skew))

    def apply(self, http_request):
        """
        Apply the authentication parameters to the outgoing HTTP request.

        Args:
            http_request: The request instance to which authentication headers
                should be added.

        """
        auth_params = {"Authorization": f"Bearer {self._oauth_token.access_token}"}
        AuthHelper.apply(auth_params, http_request.add_header)

    def _get_token_from_provider(self):
        """
        Return OAuth Token from either the user configured callbacks
            or from default provider.

        Returns:
            OauthToken: The fetched OAuth token.

        """
        if (self._oauth_token is not None
                and not self.is_token_expired(self._oauth_token)):
            return self._oauth_token

        if self._oauth_token_provider is not None:
            oauth_token = self._oauth_token_provider(self._oauth_token, self)
            self._apply_on_token_update_callback(oauth_token)
            return oauth_token

        try:
            oauth_token = self.fetch_token()
            self._apply_on_token_update_callback(oauth_token)
            return oauth_token
        except ApiException:
            return self._oauth_token

    def _apply_on_token_update_callback(self, oauth_token):
        """
        Apply the OAuth token update callback provided by the user.

        Args:
            oauth_token (OauthToken): The updated OAuth token.

        """
        if self._oauth_on_token_update is not None:
            self._oauth_on_token_update(oauth_token)


class CpdIssuedJwtCredentials:
    """
    A model for authentication credentials. Provides simple validation,
    cloning support, and optional construction from environment variables.
    Suitable as a pattern for other auth models.
    """

    @property
    def oauth_client_id(self):
        """
        OAuth 2 Client ID.
        """
        return self._oauth_client_id

    @property
    def oauth_client_secret(self):
        """
        OAuth 2 Client Secret.
        """
        return self._oauth_client_secret

    @property
    def oauth_token(self):
        """
        Object for storing information about the OAuth token.
        """
        return self._oauth_token

    @property
    def oauth_scopes(self):
        """
        List of scopes that apply to the OAuth token.
        """
        return self._oauth_scopes

    @property
    def oauth_token_provider(self):
        """
        Return the stored oauth_token_provider.
        """
        return self._oauth_token_provider

    @property
    def oauth_on_token_update(self):
        """
        Return the stored oauth_on_token_update.
        """
        return self._oauth_on_token_update

    @property
    def oauth_clock_skew(self):
        """
        Return the stored oauth_clock_skew.
        """
        return self._oauth_clock_skew

    def __init__(self, oauth_client_id, oauth_client_secret, oauth_token=None,
                 oauth_scopes=None, oauth_token_provider=None,
                 oauth_on_token_update=None, oauth_clock_skew=None):
        """
        Initialize the credentials.

        Args:
            oauth_client_id: The oauth_client_id property value to set.
            oauth_client_secret: The oauth_client_secret property value to set.
            oauth_token: The oauth_token property value to set.
            oauth_scopes: The oauth_scopes property value to set.
            oauth_token_provider: The oauth_token_provider property value to set.
            oauth_on_token_update: The oauth_on_token_update property value to set.
            oauth_clock_skew: The oauth_clock_skew property value to set.

        Raises:
            ValueError: If any required value is missing.

        """
        if oauth_client_id is None:
            raise ValueError("oauth_client_id cannot be None")
        if oauth_client_secret is None:
            raise ValueError("oauth_client_secret cannot be None")
        self._oauth_client_id = oauth_client_id
        self._oauth_client_secret = oauth_client_secret
        self._oauth_token = oauth_token
        self._oauth_scopes = oauth_scopes
        self._oauth_token_provider = oauth_token_provider
        self._oauth_on_token_update = oauth_on_token_update
        self._oauth_clock_skew = oauth_clock_skew

    def clone_with(self, oauth_client_id=None, oauth_client_secret=None,
                   oauth_token=None, oauth_scopes=None,
                   oauth_token_provider=None, oauth_on_token_update=None,
                   oauth_clock_skew=None):
        """
        Return a new instance with optional value overrides.

        Args:
            oauth_client_id: The oauth_client_id property value to set.
            oauth_client_secret: The oauth_client_secret property value to set.
            oauth_token: The oauth_token property value to set.
            oauth_scopes: The oauth_scopes property value to set.
            oauth_token_provider: The oauth_token_provider property value to set.
            oauth_on_token_update: The oauth_on_token_update property value to set.
            oauth_clock_skew: The oauth_clock_skew property value to set.

        """
        return CpdIssuedJwtCredentials(
            oauth_client_id or self.oauth_client_id,
            oauth_client_secret or self.oauth_client_secret,
            oauth_token or self.oauth_token, oauth_scopes or self.oauth_scopes,
            oauth_token_provider or self.oauth_token_provider,
            oauth_on_token_update or self.oauth_on_token_update,
            oauth_clock_skew or self.oauth_clock_skew)

    @classmethod
    def from_environment(cls):
        """
        Create credentials from environment variables, if available.

        Returns:
            A credentials instance or ``None`` if values are missing.

        """
        oauth_client_id = os.getenv(
            "CPD_ISSUED_JWT_OAUTH_CLIENT_ID",
            None,
        )
        oauth_client_secret = os.getenv(
            "CPD_ISSUED_JWT_OAUTH_CLIENT_SECRET",
            None,
        )
        oauth_scopes = os.getenv(
            "CPD_ISSUED_JWT_OAUTH_SCOPES",
            None,
        )
        oauth_clock_skew = os.getenv(
            "CPD_ISSUED_JWT_OAUTH_CLOCK_SKEW",
            None,
        )

        if (oauth_client_id is None
                or oauth_client_secret is None):
            return None

        oauth_scopes = [
            v.strip() for v in oauth_scopes.split(",") if v.strip()
        ] if oauth_scopes else None

        return cls(
            oauth_client_id=oauth_client_id,
            oauth_client_secret=oauth_client_secret,
            oauth_scopes=oauth_scopes,
            oauth_clock_skew=oauth_clock_skew,
        )
