""" Abstract Command Definition """
import json
import logging
import time
from abc import abstractmethod
from typing import Any, Optional

import requests
from pydantic import BaseModel
from requests import Response

from rowantree.common.sdk import demand_env_var_as_float

from ..contracts.dto.wrapped_request import WrappedRequest
from ..contracts.exceeded_retry_count_error import ExceededRetryCountError
from ..contracts.request_failure_error import RequestFailureError
from ..contracts.request_verb import RequestVerb


class AbstractCommand(BaseModel):
    """
    Abstract Command
    """

    sleep_time: float = 1
    retry_count: int = 5

    @abstractmethod
    def execute(self, *args, **kwargs) -> Optional[Any]:
        """Command entry point"""

    @staticmethod
    def _build_requests_params(request: WrappedRequest) -> dict:
        """
        Builds the `requests` call parameters.

        Parameters
        ----------
        request: WrappedRequest
            The request to make.

        Returns
        -------
        params: dict
            A dictionary suitable for splatting into the `requests` call.
        """

        params: dict = {
            "url": request.url,
            "timeout": demand_env_var_as_float(name="ACCESS_AUTH_ENDPOINT_TIMEOUT"),
        }
        if request.verb == RequestVerb.POST:
            params["data"] = request.data
        if request.params is not None:
            params["params"] = request.params
        return params

    def _api_caller(self, request: WrappedRequest, depth: int) -> dict:
        """
        Wrapper for calls with `requests` to external APIs.

        Parameters
        ----------
        request: WrappedRequest
            Request to make.
        depth: int
            Call depth of the recursive call (retry)

        Returns
        -------
        response: dict
            A dictionary of the response.
        """

        if depth < 1:
            raise ExceededRetryCountError(json.dumps({"request": request.dict(), "depth": depth}))
        depth -= 1

        params: dict = AbstractCommand._build_requests_params(request=request)
        # pylint: disable=broad-except
        try:
            if request.verb == RequestVerb.GET:
                response: Response = requests.get(**params)
            elif request.verb == RequestVerb.POST:
                response: Response = requests.post(**params)
            elif request.verb == RequestVerb.DELETE:
                response: Response = requests.delete(**params)
            else:
                raise Exception("Unknown Verb")
        except requests.exceptions.ConnectionError as error:
            logging.debug("Connection Error (%s) - Retrying.. %i", str(error), depth)
            time.sleep(self.sleep_time)
            return self._api_caller(request=request, depth=depth)
        except Exception as error:
            logging.debug("Exception needed to cover: %s", str(error))
            time.sleep(self.sleep_time)
            return self._api_caller(request=request, depth=depth)

        if response.status_code in request.statuses.allow:
            return response.json()

        if response.status_code in request.statuses.retry:
            time.sleep(self.sleep_time)
            return self._api_caller(request=request, depth=depth)

        raise RequestFailureError(
            json.dumps({"status_code": response.status_code, "request": request.dict(), "depth": depth})
        )

    def wrapped_request(self, request: WrappedRequest) -> dict:
        """
        High level request method.  Entry point for consumption.


        Parameters
        ----------
        request: WrappedRequest
            The request to make.

        Returns
        -------
        response: dict
            The response as a dictionary.
        """

        return self._api_caller(request=request, depth=self.retry_count)
