import json
from dataclasses import dataclass
from http import HTTPStatus

from requests import Request, Session, RequestException, HTTPError
from requests.exceptions import SSLError

from .configuration import Configuration
from .errors import IdCloudException, ForbiddenError
from .utils import JSONEncoder
from . import version


class Requestor(object):
    @dataclass
    class ResponseSnapshot(object):
        request_body: dict = None
        request_headers: dict = None
        response_body: dict = None
        response_headers: dict = None

    def __init__(self, configuration: Configuration):
        self._user_agent = f"idcloud_python/{version.__version__}"
        self._configuration = configuration
        self._session = Session()

        if self._configuration.mtls:
            self._session.cert = self._configuration.mtls

    def request(
        self, method: str, endpoint_url: str, payload: dict = None, query: dict = None
    ) -> ResponseSnapshot:
        request = Request(
            method=method,
            url=self._configuration.environment.api_url + endpoint_url,
            headers={
                "User-Agent": self._user_agent,
                "X-Api-Key": self._configuration.api_key,
                "Authorization": f"Bearer {self._configuration.jwt}"
            },
        )

        if payload and isinstance(payload, dict):
            request.data = json.dumps(payload, cls=JSONEncoder)
            request.headers['Content-Type'] = 'application/json'

        if query and isinstance(query, dict):
            request.params = query

        prepared = request.prepare()

        try:
            response = self._session.send(
                prepared,
                timeout=self._configuration.timeout
            )
        except SSLError as e:
            raise IdCloudException(request=prepared, message='SSL Error', previous=e)
        except RequestException as e:
            raise IdCloudException(request=prepared, message='Request to server timed out.', previous=e)

        try:
            response.raise_for_status()
        except HTTPError as e:
            if e.response.status_code == HTTPStatus.FORBIDDEN:
                raise ForbiddenError(
                    request=prepared, message=e.args[0], status_code=e.response.status_code, previous=e
                )
            raise IdCloudException(
                request=prepared, message=e.args[0], status_code=e.response.status_code, previous=e
            )

        response_body = response.text

        try:
            response_body = json.loads(response_body)
        except json.JSONDecodeError:
            raise IdCloudException(request=prepared, message='Cannot parse response data to json.')

        return self.ResponseSnapshot(
            request_body=json.loads(json.dumps(payload, cls=JSONEncoder)),
            request_headers=request.headers,
            response_body=response_body,
            response_headers=dict(response.headers)
        )

    def __str__(self) -> str:
        return self._user_agent
