# Copyright 2013 VMware, Inc
#
#    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 base64
import os
import xml.etree.ElementTree as et

from oslo_context import context as context_utils
from oslo_serialization import jsonutils
import requests

from vmware_nsx.plugins.nsx_v.vshield.common import exceptions


def _xmldump(obj):
    """Sort of improved xml creation method.

    This converts the dict to xml with following assumptions:
    Keys starting with _(underscore) are to be used as attributes and not
    element keys starting with @ so that dict can be made.
    Keys starting with __(double underscore) are to be skipped and its
    value is processed.
    The keys are not part of any xml schema.
    """

    config = ""
    attr = ""
    if isinstance(obj, dict):
        for key, value in obj.items():
            if key.startswith('__'):
                # Skip the key and evaluate it's value.
                a, x = _xmldump(value)
                config += x
            elif key.startswith('_'):
                attr += ' %s="%s"' % (key[1:], value)
            else:
                a, x = _xmldump(value)
                if key.startswith('@'):
                    cfg = "%s" % (x)
                else:
                    cfg = "<%s%s>%s</%s>" % (key, a, x, key)

                config += cfg
    elif isinstance(obj, list):
        for value in obj:
            a, x = _xmldump(value)
            attr += a
            config += x
    else:
        config = obj

    return attr, config


def xmldumps(obj):
    attr, xml = _xmldump(obj)
    return xml


class VcnsApiHelper(object):
    errors = {
        303: exceptions.ResourceRedirect,
        400: exceptions.RequestBad,
        403: exceptions.Forbidden,
        404: exceptions.ResourceNotFound,
        409: exceptions.ServiceConflict,
        415: exceptions.MediaTypeUnsupport,
        503: exceptions.ServiceUnavailable
    }

    nsx_errors = {
        # firewall rule doesn't exists for deletion.
        100046: exceptions.ResourceNotFound,
        100029: exceptions.ResourceNotFound,
    }

    def __init__(self, address, user, password, format='json', ca_file=None,
                 insecure=True, timeout=None):
        self.authToken = base64.encodebytes(
            bytes("%s:%s" % (user, password), 'utf-8'))
        self.user = user
        self.passwd = password
        self.address = address
        self.format = format
        self.timeout = timeout
        if format == 'json':
            self.encode = jsonutils.dumps
        else:
            self.encode = xmldumps

        if insecure:
            self.verify_cert = False
        else:
            if ca_file:
                self.verify_cert = ca_file
            else:
                self.verify_cert = True
        self._session = None
        self._pid = None

    @property
    def session(self):
        if self._session is None or self._pid != os.getpid():
            self._pid = os.getpid()
            self._session = requests.Session()
        return self._session

    def _get_nsx_errorcode(self, content):
        try:
            if self.format == 'xml':
                error = et.fromstring(content).find('errorCode')
                errcode = error is not None and int(error.text)
            else:  # json
                error = jsonutils.loads(content)
                errcode = int(error.get('errorCode'))
            return errcode
        except (TypeError, ValueError, et.ParseError):
            # We won't assume that integer error-code value is guaranteed.
            return None

    def _get_request_id(self):
        ctx = context_utils.get_current()
        if ctx:
            return ctx.__dict__.get('request_id')

    def request(self, method, uri, params=None, headers=None,
                encodeparams=True, timeout=None):
        uri = self.address + uri
        if timeout is None:
            timeout = self.timeout
        if headers is None:
            headers = {}

        auth_token = self.authToken.decode('ascii').strip()
        headers['Accept'] = 'application/' + self.format
        headers['Authorization'] = 'Basic ' + auth_token
        headers['Content-Type'] = 'application/' + self.format
        request_id = self._get_request_id()
        if request_id:
            headers['TicketNumber'] = request_id

        if params:
            if encodeparams is True:
                data = self.encode(params)
            else:
                data = params
        else:
            data = None

        try:
            response = self.session.request(method,
                                            uri,
                                            verify=self.verify_cert,
                                            data=data,
                                            headers=headers,
                                            timeout=timeout)
        except requests.exceptions.Timeout:
            raise exceptions.ResourceTimedOut(uri=uri)

        except requests.exceptions.ConnectionError:
            raise exceptions.ResourceConnectionError(uri=uri)

        status = response.status_code

        if 200 <= status < 300:
            return response.headers, response.text

        nsx_errcode = self._get_nsx_errorcode(response.text)
        if nsx_errcode in self.nsx_errors:
            cls = self.nsx_errors[nsx_errcode]
        elif status in self.errors:
            cls = self.errors[status]
        else:
            cls = exceptions.VcnsApiException
        raise cls(uri=uri, status=status,
                  header=response.headers, response=response.text)
