"""The QwikSwitch API client."""

import functools
from typing import Any

import requests
from requests.exceptions import RequestException

from .constants import DEFAULT_BASE_URI, DEFAULT_TIMEOUT, JsonKeys
from .entities import ApiKeys, ControlResult, DeviceStatuses
from .utility import ResponseParser, UrlBuilder


class QSClient:
    """The QwikSwitch API client."""

    def _ensure_authenticated(func):  # type: ignore # noqa: N805
        @functools.wraps(func)  # type: ignore
        def authenticate_if_needed(self: Any, *args: Any, **kwargs: Any):
            if self._api_keys is None:
                self.generate_api_keys()
            return func(self, *args, **kwargs)  # type: ignore

        return authenticate_if_needed

    def _handle_request_failure(func):  # type: ignore  # noqa: N805
        @functools.wraps(func)  # type: ignore
        def catch_failure(self, *args: Any, **kwargs: Any) -> Any:  # noqa: ANN001
            try:
                return func(self, *args, **kwargs)  # type: ignore
            except RequestException as ex:
                url = ex.request.url if ex.request is not None else "Unknown"
                ResponseParser.raise_request_failure(url, ex)  # type: ignore

        return catch_failure

    def __init__(
        self, email: str, master_key: str, base_uri: str = DEFAULT_BASE_URI
    ) -> None:
        """
        Initialize a new instance of the QSApi class.

        :param email: the email address to generate API keys for:param email: your email address registered on https://qwikswitch.com
        :param master_key: 12 character key found under your CloudHub.  This should be your device id of your Qwikswitch Wi-Fi bridge.
        :param base_uri: the base URI of the Qwikswitch API, optional.  Defaults to 'https://qwikswitch.com/api/v1/'
        """
        self._email = email
        self._master_key = master_key
        self._api_keys = None

        if not base_uri.endswith("/"):
            base_uri += "/"

        self._base_uri = base_uri

    @property
    def base_uri(self) -> str:
        """
        The base URI of the Qwikswitch API.

        :returns: The base URI of the Qwikswitch API
        """
        return self._base_uri

    @property
    def api_keys(self) -> ApiKeys | None:
        """
        The API keys for the QwikSwitch API.

        :returns: The API keys for the QwikSwitch API
        """
        return self._api_keys

    @api_keys.setter
    def api_keys(self, value: ApiKeys) -> None:
        """Set the API keys for the QwikSwitch API."""
        self._api_keys = value

    @_handle_request_failure  # type: ignore
    def generate_api_keys(self) -> ApiKeys:
        """
        Generate API keys for the given email and master key to be used in subsequent calls.

        :returns: APIKeys, with an API key for read operations, and one for read-write operations.
        :raises QSException: on failure to generate API keys
        """
        url = UrlBuilder.build_generate_api_keys_url(self._base_uri)
        req = {JsonKeys.EMAIL: self._email, JsonKeys.MASTER_KEY: self._master_key}

        resp = requests.post(url, json=req, timeout=DEFAULT_TIMEOUT)
        self._api_keys = ApiKeys.from_resp(resp)
        return self._api_keys

    @_handle_request_failure  # type: ignore
    def delete_api_keys(self) -> None:
        """
        Delete API keys generated for the given email and master key.

        :returns: None
        :raises QSException: on failure to delete API keys
        """
        url = UrlBuilder.build_delete_api_keys_url(self._base_uri)
        req = {JsonKeys.EMAIL: self._email, JsonKeys.MASTER_KEY: self._master_key}

        resp = requests.post(url, json=req, timeout=DEFAULT_TIMEOUT)
        _ = ApiKeys.from_resp(resp)

    @_ensure_authenticated  # type: ignore
    @_handle_request_failure  # type: ignore
    def control_device(self, device_id: str, level: int) -> ControlResult:
        """
        Control a device by setting the desired level.

        :param device_id: the unique identifier of the device to control
        :param level: this is a description of what is returned
        :returns: ControlResult, with the device and level set
        :raises QSException: when the request fails
        """
        url = UrlBuilder.build_control_url(
            self._api_keys.read_write_key,  # type: ignore
            device_id,
            level,
            self._base_uri,
        )

        resp = requests.get(url, timeout=DEFAULT_TIMEOUT)
        return ControlResult.from_resp(resp)

    @_ensure_authenticated  # type: ignore
    @_handle_request_failure  # type: ignore
    def get_all_device_status(self) -> DeviceStatuses:
        """
        Retrieve the status of all devices registered to the given API keys.

        :param auth: authentication keys generated by generate_api_keys
        :returns: Array of DeviceStatus with device information
        :raises QSException: when the request fails
        """
        url = UrlBuilder.build_get_all_device_status_url(
            self._api_keys.read_write_key,  # type: ignore
            self._base_uri,
        )

        resp = requests.get(url, timeout=DEFAULT_TIMEOUT)
        return DeviceStatuses.from_resp(resp)

    _ensure_authenticated = staticmethod(_ensure_authenticated)
    _handle_request_failure = staticmethod(_handle_request_failure)
