# -*- coding: utf-8 -*-

import json
import functools
import logging

import requests
from requests.exceptions import RequestException

from odata import version
from .exceptions import ODataError, ODataConnectionError


def catch_requests_errors(fn):
    @functools.wraps(fn)
    def inner(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except RequestException as e:
            raise ODataConnectionError(str(e))
    return inner


class ODataConnection(object):

    base_headers = {
        'Accept': 'application/json',
        'OData-Version': '4.0',
        'User-Agent': 'python-odata {0}'.format(version),
    }
    timeout = 270

    def __init__(self, session=None, auth=None):
        if session is None:
            self.session = requests.Session()
        else:
            self.session = session
        self.auth = auth
        self.log = logging.getLogger('odata.connection')

    def _apply_options(self, kwargs):
        kwargs['timeout'] = self.timeout

        if self.auth is not None:
            kwargs['auth'] = self.auth

    @catch_requests_errors
    def _do_get(self, *args, **kwargs):
        self._apply_options(kwargs)
        return self.session.get(*args, **kwargs)

    @catch_requests_errors
    def _do_post(self, *args, **kwargs):
        self._apply_options(kwargs)
        return self.session.post(*args, **kwargs)

    @catch_requests_errors
    def _do_patch(self, *args, **kwargs):
        self._apply_options(kwargs)
        return self.session.patch(*args, **kwargs)

    @catch_requests_errors
    def _do_delete(self, *args, **kwargs):
        self._apply_options(kwargs)
        return self.session.delete(*args, **kwargs)

    def _handle_odata_error(self, response):
        try:
            response.raise_for_status()
        except:
            status_code = 'HTTP {0}'.format(response.status_code)
            code = 'None'
            message = 'Server did not supply any error messages'
            detailed_message = 'None'
            response_ct = response.headers.get('content-type', '')

            if 'application/json' in response_ct:
                errordata = response.json()

                if 'error' in errordata:
                    odata_error = errordata.get('error')

                    if 'code' in odata_error:
                        code = odata_error.get('code') or code
                    if 'message' in odata_error:
                        message = odata_error.get('message') or message
                    if 'innererror' in odata_error:
                        ie = odata_error['innererror']
                        detailed_message = ie.get('message') or detailed_message

            msg = ' | '.join([status_code, code, message, detailed_message])
            err = ODataError(msg)
            err.status_code = status_code
            err.code = code
            err.message = message
            err.detailed_message = detailed_message
            raise err

    def execute_get(self, url, params=None):
        headers = {}
        headers.update(self.base_headers)

        self.log.info(u'GET {0}'.format(url))
        if params:
            self.log.info(u'Query: {0}'.format(params))

        response = self._do_get(url, params=params, headers=headers)
        self._handle_odata_error(response)
        response_ct = response.headers.get('content-type', '')
        if response.status_code == requests.codes.no_content:
            return
        if 'application/json' in response_ct:
            data = response.json()
            return data
        else:
            msg = u'Unsupported response Content-Type: {0}'.format(response_ct)
            raise ODataError(msg)

    def execute_post(self, url, data, params=None):
        headers = {
            'Content-Type': 'application/json',
        }
        headers.update(self.base_headers)

        data = json.dumps(data)

        self.log.info(u'POST {0}'.format(url))
        self.log.info(u'Payload: {0}'.format(data))

        response = self._do_post(url, data=data, headers=headers, params=params)
        self._handle_odata_error(response)
        response_ct = response.headers.get('content-type', '')
        if response.status_code == requests.codes.no_content:
            return
        if 'application/json' in response_ct:
            return response.json()
        # no exceptions here, POSTing to Actions may not return data

    def execute_patch(self, url, data):
        headers = {
            'Content-Type': 'application/json',
        }
        headers.update(self.base_headers)

        data = json.dumps(data)

        self.log.info(u'PATCH {0}'.format(url))
        self.log.info(u'Payload: {0}'.format(data))

        response = self._do_patch(url, data=data, headers=headers)
        self._handle_odata_error(response)

    def execute_delete(self, url):
        headers = {}
        headers.update(self.base_headers)

        self.log.info(u'DELETE {0}'.format(url))

        response = self._do_delete(url, headers=headers)
        self._handle_odata_error(response)
