# Copyright 2015 VMware, Inc.
# All Rights Reserved
#
#    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 requests

from neutron.i18n import _LW, _
from oslo_config import cfg
from oslo_log import log
from oslo_serialization import jsonutils

from vmware_nsx.common import exceptions as nsx_exc

LOG = log.getLogger(__name__)

ERRORS = {requests.codes.NOT_FOUND: nsx_exc.ResourceNotFound,
          requests.codes.PRECONDITION_FAILED: nsx_exc.StaleRevision}


class RESTClient(object):

    _VERB_RESP_CODES = {
        'get': [requests.codes.ok],
        'post': [requests.codes.created, requests.codes.ok],
        'put': [requests.codes.ok],
        'delete': [requests.codes.ok]
    }

    def __init__(self, host_ip=None, user_name=None,
                 password=None, insecure=None,
                 url_prefix=None, default_headers=None,
                 cert_file=None):
        self._host_ip = host_ip
        self._user_name = user_name
        self._password = password
        self._insecure = insecure if insecure is not None else False
        self._url_prefix = url_prefix or ""
        self._default_headers = default_headers or {}
        self._cert_file = cert_file

        self._session = requests.Session()
        self._session.auth = (self._user_name, self._password)
        if not insecure and self._cert_file:
            self._session.cert = self._cert_file

    def new_client_for(self, *uri_segments):
        uri = "%s/%s" % (self._url_prefix, '/'.join(uri_segments))
        uri = uri.replace('//', '/')

        return self.__class__(
            host_ip=self._host_ip, user_name=self._user_name,
            password=self._password, insecure=self._insecure,
            url_prefix=uri,
            default_headers=self._default_headers,
            cert_file=self._cert_file)

    @property
    def validate_certificate(self):
        return not self._insecure

    def list(self, headers=None):
        return self.url_list('')

    def get(self, uuid, headers=None):
        return self.url_get(uuid, headers=headers)

    def delete(self, uuid, headers=None):
        return self.url_delete(uuid, headers=headers)

    def update(self, uuid, body=None, headers=None):
        return self.url_put(uuid, body, headers=headers)

    def create(self, body=None, headers=None):
        return self.url_post('', body, headers=headers)

    def url_list(self, url, headers=None):
        return self.url_get(url, headers=headers)

    def url_get(self, url, headers=None):
        return self._rest_call(url, method='GET', headers=headers)

    def url_delete(self, url, headers=None):
        return self._rest_call(url, method='DELETE', headers=headers)

    def url_put(self, url, body, headers=None):
        return self._rest_call(url, method='PUT', body=body, headers=headers)

    def url_post(self, url, body, headers=None):
        return self._rest_call(url, method='POST', body=body, headers=headers)

    def _validate_result(self, result, expected, operation):
        if result.status_code not in expected:
            LOG.warning(_LW("The HTTP request returned error code "
                            "%(result)d, whereas %(expected)s response "
                            "codes were expected. Response body %(body)s"),
                        {'result': result.status_code,
                         'expected': '/'.join([str(code)
                                               for code in expected]),
                         'body': result.json() if result.content else ''})

            manager_error = ERRORS.get(
                result.status_code, nsx_exc.ManagerError)
            raise manager_error(manager=self._host_ip, operation=operation)

    @classmethod
    def merge_headers(cls, *headers):
        merged = {}
        for header in headers:
            if header:
                merged.update(header)
        return merged

    def _build_url(self, uri):
        uri = ("/%s/%s" % (self._url_prefix, uri)).replace('//', '/')
        return ("https://%s%s" % (self._host_ip, uri)).strip('/')

    def _rest_call(self, url, method='GET', body=None, headers=None):
        request_headers = headers.copy() if headers else {}
        request_headers.update(self._default_headers)
        request_url = self._build_url(url)

        do_request = getattr(self._session, method.lower())

        LOG.debug("REST call: %s %s\nHeaders: %s\nBody: %s",
                  method, request_url, request_headers, body)

        result = do_request(
            request_url,
            verify=self.validate_certificate,
            data=body,
            headers=request_headers,
            cert=self._cert_file)

        self._validate_result(
            result, RESTClient._VERB_RESP_CODES[method.lower()],
            _("%(verb)s %(url)s") % {'verb': method, 'url': request_url})
        return result


class JSONRESTClient(RESTClient):

    _DEFAULT_HEADERS = {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    }

    def __init__(self, host_ip=None, user_name=None,
                 password=None, insecure=None,
                 url_prefix=None, default_headers=None,
                 cert_file=None):

        super(JSONRESTClient, self).__init__(
            host_ip=host_ip, user_name=user_name,
            password=password, insecure=insecure,
            url_prefix=url_prefix,
            default_headers=RESTClient.merge_headers(
                JSONRESTClient._DEFAULT_HEADERS, default_headers),
            cert_file=cert_file)

    def _rest_call(self, *args, **kwargs):
        if kwargs.get('body') is not None:
            kwargs['body'] = jsonutils.dumps(kwargs['body'])
        result = super(JSONRESTClient, self)._rest_call(*args, **kwargs)
        return result.json() if result.content else result


class NSX3Client(JSONRESTClient):

    _NSX_V1_API_PREFIX = '/api/v1/'

    def __init__(self, host_ip=None, user_name=None,
                 password=None, insecure=None,
                 url_prefix=None, default_headers=None,
                 cert_file=None):

        url_prefix = url_prefix or NSX3Client._NSX_V1_API_PREFIX
        if (url_prefix and not url_prefix.startswith(
                NSX3Client._NSX_V1_API_PREFIX)):
            url_prefix = "%s/%s" % (NSX3Client._NSX_V1_API_PREFIX,
                                    url_prefix or '')
        host_ip = host_ip or cfg.CONF.nsx_v3.nsx_manager
        user_name = user_name or cfg.CONF.nsx_v3.nsx_user
        password = password or cfg.CONF.nsx_v3.nsx_password
        cert_file = cert_file or cfg.CONF.nsx_v3.ca_file
        insecure = (insecure if insecure is not None
                    else cfg.CONF.nsx_v3.insecure)

        super(NSX3Client, self).__init__(
            host_ip=host_ip, user_name=user_name,
            password=password, insecure=insecure,
            url_prefix=url_prefix,
            default_headers=default_headers,
            cert_file=cert_file)


# NOTE(boden): tmp until all refs use client class
def _get_client(client, *args, **kwargs):
    return client or NSX3Client(*args, **kwargs)


# NOTE(shihli): tmp until all refs use client class
def _get_manager_ip(client=None):
    # NOTE: In future this may return the IP address from a pool
    return (client._host_ip if client is not None
            else cfg.CONF.nsx_v3.nsx_manager)


# NOTE(boden): tmp until all refs use client class
def get_resource(resource, client=None):
    return _get_client(client).get(resource)


# NOTE(boden): tmp until all refs use client class
def create_resource(resource, data, client=None):
    return _get_client(client).url_post(resource, body=data)


# NOTE(boden): tmp until all refs use client class
def update_resource(resource, data, client=None):
    return _get_client(client).update(resource, body=data)


# NOTE(boden): tmp until all refs use client class
def delete_resource(resource, client=None):
    return _get_client(client).delete(resource)
