import requests
import json


class RestConfException(Exception):
    """
    Raise when a error is returned by Restconf
    """

    def __init__(self, status_code, error_tag=None, error_message=None, error_type=None, error_path=None, ):
        super().__init__()
        self.error_message = error_message
        self.error_tag = error_tag
        self.error_type = error_type
        self.error_path = error_path
        self.status_code = status_code

    def __str__(self):
        ret = f"status_code: {self.status_code}"
        if self.error_tag:
            ret += f"\nerror-tag: {self.error_tag}"
        if self.error_message:
            ret += f"\nerror-message: {self.error_message}"
        if self.error_type:
            ret += f"\nerror-type: {self.error_type}"
        if self.error_path:
            ret += f"\nerror-type: {self.error_path}"
        return ret

    def to_dict(self):
        ret = dict()
        ret['status-code'] = self.status_code
        if self.error_tag:
            ret['error-tag'] = self.error_tag
        if self.error_message:
            ret['error-message'] = self.error_message
        if self.error_type:
            ret['error-type'] = self.error_type
        if self.error_path:
            ret['error-path'] = self.error_path
        return ret


class RestConfNotFoundException(RestConfException):
    """
    Raise when an error "not found" is returned by Restconf.
    The error details may be empty when the request is correct but the node is not found
    """
    pass


class RestConf(object):
    Root = '/restconf/'
    BasePath = '/restconf/data/'
    ActionPath = '/restconf/operations/'
    QueryBase = '/restconf/tailf/query/'
    Accept = [
        'application/yang-data+json',
        'application/yang-errors+json',
    ]
    ContentType = 'application/yang-data+json'

    def __init__(self, address="localhost", port=8080, username=None, password=None, verify=True,
                 disable_warning=False, proxies=None):
        self.port = port
        self.address = address
        self.username = username
        self.password = password
        self._host = f"{self.address}:{self.port}"
        self.verify = verify
        self.proxies = proxies
        if disable_warning:
            """
            Disable warning for self signed certificate
            """
            import urllib3
            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    def build_session(self):
        session = requests.Session()
        session.verify = self.verify

        if self.username is not None and self.password is not None:
            session.auth = (self.username, self.password)
        session.headers.update({
            'Accept': ','.join([
                accept for accept in self.Accept
            ]),
            'Content-Type': self.ContentType,
        })

        if self.proxies:
            session.proxies = self.proxies

        return session

    @staticmethod
    def data_query_to_json(data_query):
        if isinstance(data_query, dict):
            data_query = json.dumps(data_query)
        return data_query

    @staticmethod
    def clean_endpoint(endpoint):
        if endpoint[0] == '/':
            endpoint = endpoint[1:]
        return endpoint

    def action(self, data_query, endpoint, query_params=None, **kwargs):
        data_query = self.data_query_to_json(data_query)
        session = self.build_session()
        endpoint = self.clean_endpoint(endpoint)
        url = self._host + self.ActionPath + endpoint
        res = session.post(url, data=data_query, params=query_params, **kwargs)
        return res

    def query(self, data_query, query_params=None, **kwargs):
        data_query = self.data_query_to_json(data_query)
        session = self.build_session()
        url = self._host + self.QueryBase
        res = session.post(url, data=data_query, params=query_params, **kwargs)
        return res

    def put(self, data_query, endpoint, query_params=None, **kwargs):
        data_query = self.data_query_to_json(data_query)
        session = self.build_session()
        endpoint = self.clean_endpoint(endpoint)
        url = self._host + self.BasePath + endpoint
        res = session.put(url, data=data_query, params=query_params, **kwargs)
        return res

    def post(self, data_query, endpoint, query_params=None, **kwargs):
        data_query = self.data_query_to_json(data_query)
        session = self.build_session()
        endpoint = self.clean_endpoint(endpoint)
        url = self._host + self.BasePath + endpoint
        res = session.post(url, data=data_query, params=query_params, **kwargs)
        return res

    def patch(self, data_query, endpoint, query_params=None, **kwargs):
        data_query = self.data_query_to_json(data_query)
        session = self.build_session()
        endpoint = self.clean_endpoint(endpoint)
        url = self._host + self.BasePath + endpoint
        res = session.patch(url, data=data_query, params=query_params, **kwargs)
        return res

    def get_root(self):
        session = self.build_session()
        url = self._host + self.Root
        res = session.get(url)
        return res

    def get(self, endpoint='', content='config', query_params=None, **kwargs):
        session = self.build_session()
        endpoint = self.clean_endpoint(endpoint)
        url = self._host + self.BasePath + endpoint
        if not query_params and content:
            query_params = {'content': content}
        elif query_params and content:
            query_params['content'] = content
        res = session.get(url, params=query_params, **kwargs)
        return res

    def delete(self, endpoint, query_params=None, **kwargs):
        session = self.build_session()
        endpoint = self.clean_endpoint(endpoint)
        url = self._host + self.BasePath + endpoint
        res = session.delete(url, params=query_params, **kwargs)
        return res

    @staticmethod
    def parse_error(res, yang_patch=False):
        if res.status_code == 404:
            exception = RestConfNotFoundException(status_code=404)
        elif res.status_code >= 500:
            exception = RestConfException(status_code=res.status_code)
            exception.error_message = res.text
        else:
            exception = RestConfException(status_code=res.status_code)
            if len(res.text) > 0:
                jres = res.json()
                if yang_patch:
                    error = jres["ietf-yang-patch:yang-patch-status"]["errors"]["error"]
                else:
                    error = jres['ietf-restconf:errors']['error']
                if len(error) > 1:
                    raise Exception(f"More than one error return when parsing restconf error. Returned: {res.text}")
                error = error[0]
                exception.error_tag = error['error-tag']
                if 'error-type' in error:
                    exception.error_type = error['error-type']
                if 'error-message' in error:
                    exception.error_message = error['error-message']
                if 'error-path' in error:
                    exception.error_path = error['error-path']

        return exception
