"""Mixins for Devices that have similar API requests.

The `BypassV2Mixin` and `BypassV1Mixin` classes are used to send API requests to the
Bypass V2 and Bypass V1 endpoints, respectively. The `BypassV2Mixin` class is used for
devices that use the `/cloud/v2/deviceManaged/bypassV2` endpoint, while the
`BypassV1Mixin` class is used for devices that use the
`/cloud/v1/deviceManaged/{endpoint}` path.
"""

from __future__ import annotations

from logging import Logger
from typing import TYPE_CHECKING, TypeVar

from mashumaro.mixins.orjson import DataClassORJSONMixin

from pyvesync.models.bypass_models import (
    RequestBypassV1,
    RequestBypassV2,
)
from pyvesync.utils.helpers import Helpers
from pyvesync.utils.logs import LibraryLogger

if TYPE_CHECKING:
    from pyvesync import VeSync
    from pyvesync.base_devices import VeSyncBaseDevice

T_MODEL = TypeVar('T_MODEL', bound=DataClassORJSONMixin)

BYPASS_V1_PATH = '/cloud/v1/deviceManaged/'
BYPASS_V2_BASE = '/cloud/v2/deviceManaged/'


def process_bypassv1_result(
    device: VeSyncBaseDevice,
    logger: Logger,
    method: str,
    resp_dict: dict | None,
    model: type[T_MODEL],
) -> T_MODEL | None:
    """Process the Bypass V1 API response.

    This will gracefully handle errors in the response and error codes,
    logging them as needed. The return value is the built model.

    Args:
        device (VeSyncBaseDevice): The device object.
        logger (Logger): The logger to use for logging.
        method (str): The method used in the payload.
        resp_dict (dict | str): The api response.
        model (type[T_MODEL]): The model to use for the response.

    Returns:
        dict: The response data
    """
    r_dict = Helpers.process_dev_response(logger, method, device, resp_dict)
    if r_dict is None or 'result' not in r_dict:
        return None
    result = r_dict['result']
    return Helpers.model_maker(logger, model, method, result, device)


def _get_inner_result(
    device: VeSyncBaseDevice,
    logger: Logger,
    method: str,
    resp_dict: dict,
) -> dict | None:
    """Process the code in the result field of Bypass V2."""
    try:
        outer_result = resp_dict['result']
        inner_result = outer_result['result']
        code = int(outer_result['code'])
    except (ValueError, TypeError, KeyError):
        LibraryLogger.error_device_response_content(
            logger,
            device,
            method,
            'Error processing bypass V2 API response result.',
        )
        return None

    if code != 0:
        return None
    return inner_result


def process_bypassv2_result(
    device: VeSyncBaseDevice,
    logger: Logger,
    method: str,
    resp_dict: dict | None,
    model: type[T_MODEL],
) -> T_MODEL | None:
    """Process the Bypass V1 API response.

    This will gracefully handle errors in the response and error codes,
    logging them as needed. The return dictionary is the **inner** result value of
    the API response.

    Args:
        device (VeSyncBaseDevice): The device object.
        logger (Logger): The logger to use for logging.
        method (str): The method used in the payload.
        resp_dict (dict | str): The api response.
        model (type[T_MODEL]): The model to use for the response.

    Returns:
        T_MODEL: An instance of the inner result model.
    """
    r_dict = Helpers.process_dev_response(logger, method, device, resp_dict)
    if r_dict is None:
        return None
    result = _get_inner_result(device, logger, method, r_dict)
    if not isinstance(result, dict):
        return None
    return Helpers.model_maker(logger, model, method, result, device)


class BypassV2Mixin:
    """Mixin for bypass V2 API.

    Overrides the `_build_request` method and `request_keys` attribute for devices
    that use the Bypass V2 API- /cloud/v2/deviceManaged/bypassV2.
    """

    if TYPE_CHECKING:
        manager: VeSync

    __slots__ = ()
    request_keys: tuple[str, ...] = (
        'acceptLanguage',
        'appVersion',
        'phoneBrand',
        'phoneOS',
        'accountID',
        'cid',
        'configModule',
        'debugMode',
        'traceId',
        'timeZone',
        'token',
        'userCountryCode',
        'configModel',
        'deviceId',
    )

    def _build_request(
        self,
        payload_method: str,
        data: dict | None = None,
        method: str = 'bypassV2',
        payload_update: dict | None = None,
    ) -> RequestBypassV2:
        """Build API request body Bypass V2 endpoint.

        Args:
            payload_method (str): The method to use in the payload dict.
            data (dict | None): The data dict inside the payload value.
            method (str): The method to use in the outer body, defaults to bypassV2.
            payload_update (dict | None): Additional keys to add to the payload.
        """
        body = {}
        body = Helpers.get_defaultvalues_attributes(self.request_keys).copy()
        body.update(Helpers.get_manager_attributes(self.manager, self.request_keys))
        body.update(Helpers.get_device_attributes(self, self.request_keys))
        body['method'] = method
        body['payload'] = {'method': payload_method, 'source': 'APP', 'data': data or {}}
        body['payload'] |= payload_update or {}
        return RequestBypassV2.from_dict(body)

    async def call_bypassv2_api(
        self,
        payload_method: str,
        data: dict | None = None,
        method: str = 'bypassV2',
        endpoint: str = 'bypassV2',
        payload_update: dict | None = None,
    ) -> dict | None:
        """Send Bypass V2 API request.

        This uses the `_build_request` method to send API requests to the Bypass V2 API.

        Args:
            payload_method (str): The method to use in the payload dict.
            data (dict | None): The data to send in the request.
            method (str): The method to use in the outer body.
            endpoint (str | None): The last part of the API url, defaults to
                `bypassV2`, e.g. `/cloud/v2/deviceManaged/bypassV2`.
            payload_update (dict | None): Additional keys to add to the payload.

        Returns:
            bytes: The response from the API request.
        """
        request = self._build_request(payload_method, data, method, payload_update)
        endpoint = BYPASS_V2_BASE + endpoint
        resp_dict, _ = await self.manager.async_call_api(
            endpoint, 'post', request, Helpers.req_header_bypass()
        )
        return resp_dict


class BypassV1Mixin:
    """Mixin for bypass V1 API.

    Overrides the `_build_request` method and `request_keys` attribute for devices
    that use the Bypass V1 API- /cloud/v1/deviceManaged/bypass. The primary method to
    call is `call_bypassv1_api`, which is a wrapper for setting up the request body and
    calling the API. The `bypass` endpoint can also be overridden for specific API calls.
    """

    if TYPE_CHECKING:
        manager: VeSync

    __slots__ = ()
    request_keys: tuple[str, ...] = (
        'acceptLanguage',
        'appVersion',
        'phoneBrand',
        'phoneOS',
        'accountID',
        'cid',
        'configModule',
        'debugMode',
        'traceId',
        'timeZone',
        'token',
        'userCountryCode',
        'uuid',
        'configModel',
        'deviceId',
    )

    def _build_request(
        self,
        request_model: type[RequestBypassV1],
        update_dict: dict | None = None,
        method: str = 'bypass',
    ) -> RequestBypassV1:
        """Build API request body for the Bypass V1 endpoint.

        Args:
            request_model (type[RequestBypassV1]): The request model to use.
            update_dict (dict | None): Additional keys to add on.
            method (str): The method to use in the outer body, defaults to bypass.

        Returns:
            RequestBypassV1: The request body for the Bypass V1 endpoint, the correct
            model is determined from the RequestBypassV1 discriminator.
        """
        body = Helpers.get_defaultvalues_attributes(self.request_keys).copy()
        body.update(Helpers.get_manager_attributes(self.manager, self.request_keys))
        body.update(Helpers.get_device_attributes(self, self.request_keys))
        body['method'] = method
        body.update(update_dict or {})
        return request_model.from_dict(body)

    async def call_bypassv1_api(
        self,
        request_model: type[RequestBypassV1],
        update_dict: dict | None = None,
        method: str = 'bypass',
        endpoint: str = 'bypass',
    ) -> dict | None:
        """Send ByPass V1 API request.

        This uses the `_build_request` method to send API requests to the Bypass V1 API.
        The endpoint can be overridden with the `endpoint` argument.

        Args:
            request_model (type[RequestBypassV1]): The request model to use.
            update_dict (dict): Additional keys to add on.
            method (str): The method to use in the outer body.
            endpoint (str | None): The last part of the url path, defaults to
                `bypass`, e.g. `/cloud/v1/deviceManaged/bypass`.

        Returns:
            bytes: The response from the API request.
        """
        request = self._build_request(request_model, update_dict, method)
        url_path = BYPASS_V1_PATH + endpoint
        resp_dict, _ = await self.manager.async_call_api(
            url_path, 'post', request, Helpers.req_header_bypass()
        )

        return resp_dict
