"""The PowerPanel Python API"""

import enum
from typing import Union
import json
import platform
import requests

__all__ = ["PowerPanel", "PowerPanelResponse"]


class _PowerPanelAuth(requests.auth.AuthBase):
    """Attaches an Authorization header to a PowerPanel request"""

    def __init__(self, key_file_path: str):
        """Initializes the Auth generator with the key file"""
        self.key_file_path = key_file_path

    def _get_api_key(self) -> str:
        """Reads the pp API key and returns the raw key data.

        Raises IOError if the key file does not exist or cannot be read.
        """
        with open(self.key_file_path, encoding='ascii') as key_data:
            return "".join(key_data.read().strip().split())

    def __call__(self, r):
        """Modifies request adding the auth header"""
        r.headers['Authorization'] = f'{platform.node()}:{self._get_api_key()}'
        return r


class PowerPanelResponse:
    """The response object from a PowerPanel call

    Attributes:
        status (int | Status):
            The HTTP status code returned by the API, or ``-1`` if no response
            was acquired. Will be a Status object if PowerPanel was initialized
            with enum_status=True
        message (str):
            The message from the PowerPanel API,
            or "ERROR: [message]" if status is ``-1``.
        data (dict):
            any data from the API
        raw_response (requests.Response):
            The requests object returned after querying the API,
            or ``None`` if status is ``-1``
    """

    def _generate_error(self, message, request_object=None):
        """Sets self properties to handle error with [message]"""
        self.status = -1
        self.message = f"ERROR: {message}"
        self.data = {}
        self.raw_response = request_object

    def _status_to_enum(self):
        try:
            self.status = Status(self.status)
        except ValueError:
            self.status = Status.UNKNOWN_STATUS

    def __init__(
        self,
        request_object: Union[requests.Response, requests.RequestException],
        enum_status: bool,
    ):
        """Parses a requests object containing the power panel API response and
        populates this object's properties.
        """
        if isinstance(request_object, requests.RequestException):
            self._generate_error(
                f"Could not get response from PowerPanel: {request_object}"
            )
            if enum_status:
                self._status_to_enum()
            return
        try:
            json_response = request_object.json()
        except ValueError:
            self._generate_error(
                "Could not parse response from PowerPanel", request_object
            )
            if enum_status:
                self._status_to_enum()
            return
        try:
            self.status = json_response['status']
            self.message = json_response['message']
            self.data = json_response['data']
            self.raw_response = request_object
        except KeyError:
            self._generate_error(
                "Could not parse response from PowerPanel", request_object
            )
        if enum_status:
            self._status_to_enum()


class PowerPanel:
    """A Pythonic interface to the PowerPanel API.

    Normally, one would use only the "call" method

    Args:
        key_file_path (str): path to file containing the PowerPanel API key
        base_url (str): URL to PowerPanel. Change this only for testing
    References:
        https://trac.imhtech.net/Development/wiki/InMotion/Projects/PowerPanelAPI/CommandReference
    Note:
        Calling this object directly will redirect to self.call()
    """

    def __init__(
        self,
        key_file_path: str = '/etc/pp_api.key',
        base_url: str = 'https://secure1.inmotionhosting.com/api',
        enum_status: bool = False,
    ):
        self.auth = _PowerPanelAuth(key_file_path)
        self.pp_api_base_url = base_url
        self.enum_status = enum_status

    def call(
        self, api_name: str, timeout=30, **argument_dict
    ) -> PowerPanelResponse:
        """Makes a call to the powerpanel API

        Args:
            api_name: dot or slash separated name of the PowerPanel command.

                The following would be equivalent:

                - reporting.provisioning
                - reporting/provisioning

            argument_dict: any other arguments to pass to the API as data
        """
        api_name = api_name.replace('.', '/')
        headers = {'Content-Type': 'application/json'}
        try:
            obj = requests.post(
                f"{self.pp_api_base_url}/{api_name}",
                data=json.dumps(argument_dict),
                timeout=timeout,
                headers=headers,
                auth=self.auth,
            )
        except requests.RequestException as exc:
            obj = exc
        return PowerPanelResponse(obj, self.enum_status)

    def __call__(self, api_name: str, **argument_dict) -> PowerPanelResponse:
        """Wraps the call function, making an instance of this class directly
        callable"""
        return self.call(api_name, **argument_dict)


class Status(enum.Enum):
    """PowerPanel API Status Codes"""

    # No response from PowerPanel - added by pp_api.PowerPanelResponse()
    NO_RESPONSE = -1

    # PowerPanel gave a status code not listed in their documentation - added
    # by pp_api.PowerPanelResponse()
    UNKNOWN_STATUS = -2

    # All operations performed without errors
    STATUS_OK = 0

    # The agent that made the request is not authorized for the given request.
    # Typically, this indicates improper Authorization header content.
    NOT_AUTHORIZED = 200

    # A required named parameter for the requested API function was not provided
    # in the request
    MISSING_PARAMETER = 305

    # A named parameter value does not have the expected data format
    INVALID_INPUT = 307

    # A client requested an invalid or undefined API command name
    INVALID_COMMAND = 308

    # The request body text is not a proper JSON-encoded parameter set.
    REQUEST_FORMAT_ERROR = 310

    # A generic error indiciating a model created/updated by the command
    # resulted did not pass validation
    VALIDATION_FAILURE = 311

    # A generic error indicating a specified resource in the power panel system
    # could not be identified from the given parameters
    UNKNOWN_RESOURCE = 312

    # A generic error indicating a request cannot be processed due to an
    # inappropriate state in the data objects targetted by the request.
    DATA_STATE_ERROR = 400

    # An administrative request tried to register a client server name that has
    # already been registered with power panel without specifying an overwrite
    # flag
    SERVER_ALREADY_REGISTERED = 410

    # An uncaught exception came up during the processing of an API request.
    INTERNAL_SERVER_ERROR = 500

    # Error signifying that a particular command has not been implemented yet.
    # This is mainly for use in development.
    NOT_IMPLEMENTED = 501
