#    Copyright 2020 Jonas Waeber
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
import json
from typing import Dict, Any, Iterable, Optional

import requests
from requests import RequestException

from pywaclient.exceptions import AccessForbidden, ResourceNotFound, InternalServerException, \
    UnexpectedStatusException, ConnectionException, UnprocessableDataProvided


def _parse_response(path, response) -> Dict[str, Any]:
    if response.ok:
        data = response.json()
        if data['success']:
            return data
        else:
            raise UnexpectedStatusException(response.status_code, data['error'], path)
    elif response.status_code == 403:
        raise AccessForbidden(path)
    elif response.status_code == 404:
        raise ResourceNotFound(path)
    elif response.status_code == 422:
        raise UnprocessableDataProvided(path, response.json())
    elif response.status_code == 500:
        raise InternalServerException(path)
    else:
        raise UnexpectedStatusException(response.status_code, response.reason, path)


class Endpoint:

    def __init__(self, client: 'AragornApiClient'):
        self.client = client

    def _get_request(self, path: str, params: Dict[str, str]) -> Dict[str, Any]:
        try:
            response = requests.get(self.client.base_url + path, params=params, headers=self.client.headers)
            return _parse_response(path, response)
        except RequestException as err:
            raise ConnectionException(str(err))

    def _put_request(self, path: str, content: Dict[str, Any]) -> Dict[str, Any]:
        try:
            response = requests.put(self.client.base_url + path, json=content, headers=self.client.headers_post)
            return _parse_response(path, response)
        except RequestException as err:
            raise ConnectionException(str(err))

    def _patch_request(self, path: str, params: Dict[str, str], content: Dict[str, Any]) -> Dict[str, Any]:
        try:
            response = requests.patch(self.client.base_url + path, params=params, json=content,
                                      headers=self.client.headers_post)
            return _parse_response(path, response)
        except RequestException as err:
            raise ConnectionException(str(err))

    def _post_request(self, path: str, params: Dict[str, str], content: Dict[str, Any]) -> Dict[str, Any]:
        try:
            response = requests.post(self.client.base_url + path, params=params, json=content,
                                     headers=self.client.headers_post)
            return _parse_response(path, response)
        except RequestException as err:
            raise ConnectionException(str(err))

    def _delete_request(self, path: str, params: Dict[str, str]):
        try:
            response = requests.delete(self.client.base_url + path,
                                       params=params,
                                       headers=self.client.headers)
            return _parse_response(path, response)
        except RequestException as err:
            raise ConnectionException(str(err))

    def _scroll_collection(self, path: str, params: Dict[str, str], collection_tag: str, parentName: str = '', parentId: str = '') -> Iterable[Dict[str, Any]]:
        limit = 50
        offset = 0
        if parentName != '':
            content = {'limit': limit, 'offset': offset, parentName: {'id': parentId}}
        else:
            content = {'limit': limit, 'offset': offset}
        collection = self._post_request(path, params, content)
        if collection['success']:
            items = collection[collection_tag]
            while len(items) > 0:
                for item in items:
                    yield item
                offset = offset + limit
                content['offset'] = offset
                collection = self._post_request(path, params, content)
                items = collection[collection_tag]

    @staticmethod
    def _download_binary(url: str, chunk_size: int = 8192) -> Iterable[bytes]:
        with requests.get(url, stream=True) as r:
            r.raise_for_status()
            for chunk in r.iter_content(chunk_size=chunk_size):
                yield chunk
