import pkg_resources
import requests
import six
import urllib3
from google.protobuf.message import DecodeError

from yamcs.api import exception_pb2
from yamcs.core.exceptions import (ConnectionFailure, NotFound, Unauthorized,
                                   YamcsError)


class BaseClient(object):

    credentials = None

    def __init__(self, address, tls=False, credentials=None, user_agent=None,
                 on_token_update=None, tls_verify=True):
        if ':' in address:
            self.address = address
        else:
            self.address = address + ':8090'

        if tls:
            self.auth_root = 'https://{}/auth'.format(self.address)
            self.api_root = 'https://{}/api'.format(self.address)
            self.ws_root = 'wss://{}/_websocket'.format(self.address)
            self.ws_root_v2 = 'wss://{}/api/websocket'.format(self.address)
        else:
            self.auth_root = 'http://{}/auth'.format(self.address)
            self.api_root = 'http://{}/api'.format(self.address)
            self.ws_root = 'ws://{}/_websocket'.format(self.address)
            self.ws_root_v2 = 'ws://{}/api/websocket'.format(self.address)

        self.session = requests.Session()
        if not tls_verify:
            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
            self.session.verify = False

        if credentials:
            self.credentials = credentials.login(
                self.session, self.auth_root, on_token_update)

        if not user_agent:
            dist = pkg_resources.get_distribution('yamcs-client')
            user_agent = 'yamcs-python-client v' + dist.version
        self.session.headers.update({'User-Agent': user_agent})

    def get_proto(self, path, **kwargs):
        headers = kwargs.pop('headers', {})
        headers['Accept'] = 'application/protobuf'
        kwargs.update({'headers': headers})
        return self.request('get', path, **kwargs)

    def put_proto(self, path, **kwargs):
        headers = kwargs.pop('headers', {})
        headers['Content-Type'] = 'application/protobuf'
        headers['Accept'] = 'application/protobuf'
        kwargs.update({'headers': headers})
        return self.request('put', path, **kwargs)

    def patch_proto(self, path, **kwargs):
        headers = kwargs.pop('headers', {})
        headers['Content-Type'] = 'application/protobuf'
        headers['Accept'] = 'application/protobuf'
        kwargs.update({'headers': headers})
        return self.request('patch', path, **kwargs)

    def post_proto(self, path, **kwargs):
        headers = kwargs.pop('headers', {})
        headers['Content-Type'] = 'application/protobuf'
        headers['Accept'] = 'application/protobuf'
        kwargs.update({'headers': headers})
        return self.request('post', path, **kwargs)

    def delete_proto(self, path, **kwargs):
        headers = kwargs.pop('headers', {})
        headers['Accept'] = 'application/protobuf'
        kwargs.update({'headers': headers})
        return self.request('delete', path, **kwargs)

    def request(self, method, path, **kwargs):
        path = '{}{}'.format(self.api_root, path)

        if self.credentials:
            self.credentials.before_request(self.session, self.auth_root)

        try:
            response = self.session.request(method, path, **kwargs)
        except requests.exceptions.SSLError as sslError:
            msg = 'Connection to {} failed: {}'.format(self.address, sslError)
            six.raise_from(ConnectionFailure(msg), None)
        except requests.exceptions.ConnectionError as e:
            raise ConnectionFailure('Connection to {} failed: {}'.format(
                self.address, e))

        if 200 <= response.status_code < 300:
            return response

        exception_message = exception_pb2.ExceptionMessage()
        try:
            exception_message.ParseFromString(response.content)
        except DecodeError:
            pass

        if response.status_code == 401:
            raise Unauthorized('401 Client Error: Unauthorized')
        elif response.status_code == 404:
            raise NotFound('404 Client Error: {}'.format(
                getattr(exception_message, 'msg')))
        elif 400 <= response.status_code < 500:
            raise YamcsError('{} Client Error: {}'.format(
                response.status_code, getattr(exception_message, 'msg')))
        raise YamcsError('{} Server Error: {}'.format(
            response.status_code, getattr(exception_message, 'msg')))
