import requests
import logging
import time
from .nodes import Nodes
from .models import Models
from .workloads import Workloads

# Define constants
_API_KEY_EXPIRY_IN_SECS = 240
_VERSION = "1.0.0"

class ApiClient:
    def __init__(self, client_id, client_secret, username, password):
        """
        ### Description
        Initialize the ApiClient with the necessary credentials and setup logging.
        
        ### Parameters
        
        * **client_id** (str): The client ID for authentication.
        * **client_secret** (str): The client secret for authentication.
        * **username** (str): The username for authentication.
        * **password** (str): The password for authentication.
        """
        # Set the base URL and token URL for API requests
        self._base_url = 'https://prod.bap.barbaraiot.com/api/v1'
        self._token_url = 'https://prod.auth.barbaraiot.com/realms/bbr_prod/protocol/openid-connect/token'
        
        # Store the provided credentials
        self._client_id = client_id
        self._client_secret = client_secret
        self._username = username
        self._password = password
        
        # Set up logging for the class
        self.log = logging.getLogger(__name__)
        """
        ApiClient logger instance
        """
        self.log.info(f"Barbara Python SDK. Version {_VERSION}.")
        
        # Initialize the API key timestamp
        self._api_key_timestamp = 0
        # Refresh the API key upon initialization
        self._refresh_api_key()
        
        # Initialize instances of Nodes, Models, and Workloads
        self.nodes = Nodes(self)
        """
        Nodes instance
        """
        self.models = Models(self)
        """
        Models instance
        """
        self.workloads = Workloads(self)
        """
        Workloads instance
        """
        return

    def _refresh_api_key(self):
        """
        Refresh the API key by making a request to the token URL.
        
        No parameters.
        
        Returns:
        None
        """
        # Prepare the payload for the request
        params = None
        headers = None
        data = {
            'client_id': self._client_id,
            'grant_type': 'password',
            'client_secret': self._client_secret,
            'username': self._username,
            'password': self._password
            }

        # Make a POST request to the token URL and capture any exception
        try:
            response = requests.request('POST', self._token_url, params=params, data=data, headers=headers)
            response.raise_for_status() # Raise an exception for unsuccessful HTTP status codes
            response_json = response.json()
            self.api_key = response_json['access_token']
            self._api_key_timestamp = time.time()
        except KeyError: # Handles the 'response' key error case
            self.log.error(f"Error finding access token field in JSON.")
        except requests.exceptions.JSONDecodeError: # Handles all .json error cases
            self.log.error(f"Error decoding response JSON.")
        except requests.exceptions.HTTPError as errh: # Handles all HTTP error cases, raised by response.raise_for_status()
            self.log.error(f"HTTP error: {errh}.")
        except requests.exceptions.RequestException as errr: # Handles the rest of .requests error cases
            self.log.error(f"Request exception: {errr}.")
        else:
            self.log.info(f"API Key refreshed.")

        return

    def _make_request(self, method, endpoint, params=None, json=None, data=None, headers=None, files=None):
        """
        Make an HTTP request to the specified endpoint.
        
        Parameters:
        method (str): The HTTP method to use for the request (e.g., 'GET', 'POST').
        endpoint (str): The API endpoint to call.
        params (dict, optional): The query parameters to include in the request.
        json (dict, optional): The JSON data to include in the request body.
        data (dict, optional): The form data to include in the request body.
        headers (dict, optional): The headers to include in the request.
        files (dict, optional): The files to include in the request.
        
        Returns:
        dict: The JSON response from the API call, or None if an error occurs.
        """
        # Get the current time
        current_time = time.time()
        
        # Refresh the API key if it has expired
        if (current_time - self._api_key_timestamp) > _API_KEY_EXPIRY_IN_SECS:
            self._refresh_api_key()
            
        # Construct the full URL for the request
        url = f"{self._base_url}/{endpoint}"
        
        # Add the Authorization header
        headers = headers or {}
        headers['Authorization'] = f"Bearer {self.api_key}"
        
        # If JSON data is provided, use it as the request body and set the Content-Type header
        if json:
            data = json
            headers['Content-Type'] = 'application/json'            

        result = None

        # Make the HTTP request with the specified method and parameters and capture any exception
        try:
            response = requests.request(method, url, params=params, data=data, headers=headers, files=files)
            response.raise_for_status() # Raise an exception for unsuccessful HTTP status codes
            response_json = response.json()
            result = response_json['response']
        except KeyError: # Handles the 'response' key error case
            self.log.error(f"Error finding response field in JSON.")
        except requests.exceptions.JSONDecodeError: # Handles all .json error cases
            self.log.error(f"Error decoding response JSON.")
        except requests.exceptions.HTTPError as errh: # Handles all HTTP error cases, raised by response.raise_for_status()
            self.log.error(f"HTTP error: {errh}.")
        except requests.exceptions.RequestException as errr: # Handles the rest of .requests error cases
            self.log.error(f"Request exception: {errr}.")
        else:
            self.log.debug(f"HTTP response - Method: {method} - Endpoint: {endpoint} - Result: {result}.")

        # Return the result of the request
        return result

    def get_version(self):
        """
        ### Description
        Log and return the version of the SDK.
        
        ### Parameters
        None.
        
        ### Returns
        * **version** (str): The version of the SDK.
        """
        # Log the SDK version
        self.log.info(f"ApiClient.get_version() command executed by the user")
        self.log.info(f"Barbara Python SDK. Version {_VERSION}.")
        
        # Return the SDK version
        return _VERSION
