
"""
pay_quicker_sdk

This file was automatically generated for PayQuicker 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 payquickersdk.api_helper import APIHelper
from payquickersdk.controllers.o_auth_authorization_controller import (
    OAuthAuthorizationController,
)
from payquickersdk.exceptions.api_exception import (
    APIException,
)
from payquickersdk.models.o_auth_token import (
    OAuthToken,
)


class Server(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 "Server: 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._o_auth_client_id = auth_credentials_model.o_auth_client_id \
            if auth_credentials_model is not None else None
        self._o_auth_client_secret = auth_credentials_model.o_auth_client_secret \
            if auth_credentials_model is not None else None
        if auth_credentials_model is not None \
                and isinstance(auth_credentials_model.o_auth_token, OAuthToken):
            self._o_auth_token = OAuthToken.from_dictionary(
                APIHelper.to_dictionary(auth_credentials_model.o_auth_token))
        else:
            self._o_auth_token = auth_credentials_model.o_auth_token \
                if auth_credentials_model is not None else None
        if auth_credentials_model is not None \
                and isinstance(auth_credentials_model.o_auth_scopes, list):
            self._o_auth_scopes = auth_credentials_model.o_auth_scopes
        else:
            self._o_auth_scopes = None
        self._o_auth_clock_skew = auth_credentials_model.o_auth_clock_skew \
            if auth_credentials_model is not None else None
        self._o_auth_token_provider = auth_credentials_model.o_auth_token_provider \
            if auth_credentials_model is not None else None
        self._o_auth_on_token_update = auth_credentials_model.o_auth_on_token_update \
            if auth_credentials_model is not None else None
        self._o_auth_api = OAuthAuthorizationController(config)
        super().__init__(auth_params={})

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

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

        """
        self._o_auth_token = self._get_token_from_provider()
        return (self._o_auth_token and isinstance(self._o_auth_token, OAuthToken)
                and not self.is_token_expired(self._o_auth_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._o_auth_client_id,
            self._o_auth_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_server(
            self.build_basic_auth_header(),
            " ".join(self._o_auth_scopes) if self._o_auth_scopes else None,
            _optional_form_parameters=additional_params,
        )
        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, o_auth_token=None):
        """
        Check if OAuth token has expired.

        Args:
            o_auth_token (OAuthToken): The OAuth token whose expiry is to be checked.

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

        """
        if o_auth_token is None:
            return (hasattr(self._o_auth_token, "expiry")
                    and AuthHelper.is_token_expired(
                        self._o_auth_token.expiry, self._o_auth_clock_skew))

        return (hasattr(o_auth_token, "expiry")
                and AuthHelper.is_token_expired(
                    o_auth_token.expiry, self._o_auth_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._o_auth_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._o_auth_token is not None
                and not self.is_token_expired(self._o_auth_token)):
            return self._o_auth_token

        if self._o_auth_token_provider is not None:
            o_auth_token = self._o_auth_token_provider(self._o_auth_token, self)
            self._apply_on_token_update_callback(o_auth_token)
            return o_auth_token

        try:
            o_auth_token = self.fetch_token()
            self._apply_on_token_update_callback(o_auth_token)
            return o_auth_token
        except APIException:
            return self._o_auth_token

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

        Args:
            o_auth_token (OAuthToken): The updated OAuth token.

        """
        if self._o_auth_on_token_update is not None:
            self._o_auth_on_token_update(o_auth_token)


class ServerCredentials:
    """
    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 o_auth_client_id(self):
        """
        OAuth 2 Client ID.
        """
        return self._o_auth_client_id

    @property
    def o_auth_client_secret(self):
        """
        OAuth 2 Client Secret.
        """
        return self._o_auth_client_secret

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

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

    @property
    def o_auth_token_provider(self):
        """
        Return the stored o_auth_token_provider.
        """
        return self._o_auth_token_provider

    @property
    def o_auth_on_token_update(self):
        """
        Return the stored o_auth_on_token_update.
        """
        return self._o_auth_on_token_update

    @property
    def o_auth_clock_skew(self):
        """
        Return the stored o_auth_clock_skew.
        """
        return self._o_auth_clock_skew

    def __init__(self, o_auth_client_id, o_auth_client_secret,
                 o_auth_token=None, o_auth_scopes=None,
                 o_auth_token_provider=None, o_auth_on_token_update=None,
                 o_auth_clock_skew=None):
        """
        Initialize the credentials.

        Args:
            o_auth_client_id: The o_auth_client_id property value to set.
            o_auth_client_secret: The o_auth_client_secret property value to set.
            o_auth_token: The o_auth_token property value to set.
            o_auth_scopes: The o_auth_scopes property value to set.
            o_auth_token_provider: The o_auth_token_provider property value to set.
            o_auth_on_token_update: The o_auth_on_token_update property value to set.
            o_auth_clock_skew: The o_auth_clock_skew property value to set.

        Raises:
            ValueError: If any required value is missing.

        """
        if o_auth_client_id is None:
            raise ValueError("o_auth_client_id cannot be None")
        if o_auth_client_secret is None:
            raise ValueError("o_auth_client_secret cannot be None")
        self._o_auth_client_id = o_auth_client_id
        self._o_auth_client_secret = o_auth_client_secret
        self._o_auth_token = o_auth_token
        self._o_auth_scopes = o_auth_scopes
        self._o_auth_token_provider = o_auth_token_provider
        self._o_auth_on_token_update = o_auth_on_token_update
        self._o_auth_clock_skew = o_auth_clock_skew

    def clone_with(self, o_auth_client_id=None, o_auth_client_secret=None,
                   o_auth_token=None, o_auth_scopes=None,
                   o_auth_token_provider=None, o_auth_on_token_update=None,
                   o_auth_clock_skew=None):
        """
        Return a new instance with optional value overrides.

        Args:
            o_auth_client_id: The o_auth_client_id property value to set.
            o_auth_client_secret: The o_auth_client_secret property value to set.
            o_auth_token: The o_auth_token property value to set.
            o_auth_scopes: The o_auth_scopes property value to set.
            o_auth_token_provider: The o_auth_token_provider property value to set.
            o_auth_on_token_update: The o_auth_on_token_update property value to set.
            o_auth_clock_skew: The o_auth_clock_skew property value to set.

        """
        return ServerCredentials(
            o_auth_client_id or self.o_auth_client_id,
            o_auth_client_secret or self.o_auth_client_secret,
            o_auth_token or self.o_auth_token,
            o_auth_scopes or self.o_auth_scopes,
            o_auth_token_provider or self.o_auth_token_provider,
            o_auth_on_token_update or self.o_auth_on_token_update,
            o_auth_clock_skew or self.o_auth_clock_skew)

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

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

        """
        o_auth_client_id = os.getenv(
            "SERVER_O_AUTH_CLIENT_ID",
            None,
        )
        o_auth_client_secret = os.getenv(
            "SERVER_O_AUTH_CLIENT_SECRET",
            None,
        )
        o_auth_scopes = os.getenv(
            "SERVER_O_AUTH_SCOPES",
            None,
        )
        o_auth_clock_skew = os.getenv(
            "SERVER_O_AUTH_CLOCK_SKEW",
            None,
        )

        if (o_auth_client_id is None
                or o_auth_client_secret is None):
            return None

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

        return cls(
            o_auth_client_id=o_auth_client_id,
            o_auth_client_secret=o_auth_client_secret,
            o_auth_scopes=o_auth_scopes,
            o_auth_clock_skew=o_auth_clock_skew,
        )
