# Copyright 2015 Infoblox 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.
from urllib import parse

from oslo_config import cfg
from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import strutils
import requests

from designate.backend.impl_infoblox import ibexceptions as exc

CFG_GROUP_NAME = 'backend:infoblox'
CONF = cfg.CONF
LOG = log.getLogger(__name__)


class Infoblox(object):
    """Infoblox class

    Defines methods for getting, creating, updating and
    removing objects from an Infoblox server instance.
    """

    def __init__(self, options):
        """Initialize a new Infoblox object instance

        Args:
            options (dict): Target options dictionary
        """
        config = CONF[CFG_GROUP_NAME]

        reqd_opts = ['wapi_url', 'username', 'password', 'ns_group']
        other_opts = ['sslverify', 'network_view', 'dns_view', 'multi_tenant']

        for opt in reqd_opts + other_opts:
            if opt == 'sslverify' or opt == 'multi_tenant':
                # NOTE(selvakumar): This check is for sslverify option.
                # type of sslverify is unicode string from designate DB
                # if the value is 0 getattr called for setting default values.
                # to avoid setting default values we use oslo strutils
                if not strutils.is_int_like(options.get(opt)):
                    option_value = options.get(opt)
                else:
                    option_value = strutils.bool_from_string(options.get(opt),
                                                             default=True)
                setattr(self, opt, option_value)
                continue
            setattr(self, opt, options.get(opt) or getattr(config, opt))

        for opt in reqd_opts:
            LOG.debug("self.%s = %s", opt, getattr(self, opt))
            if not getattr(self, opt):
                raise exc.InfobloxIsMisconfigured(option=opt)

        self.session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(
            pool_connections=config.http_pool_connections,
            pool_maxsize=config.http_pool_maxsize)
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)
        self.session.auth = (self.username, self.password)
        self.session.verify = self.sslverify

    def _construct_url(self, relative_path, query_params=None, extattrs=None):
        if query_params is None:
            query_params = {}
        if extattrs is None:
            extattrs = {}

        if not relative_path or relative_path[0] == '/':
            raise ValueError('Path in request must be relative.')
        query = ''
        if query_params or extattrs:
            query = '?'

        if extattrs:
            attrs_queries = []
            for key, value in extattrs.items():
                LOG.debug("key: %s, value: %s", key, value)
                attrs_queries.append('*' + key + '=' + value['value'])
            query += '&'.join(attrs_queries)
        if query_params:
            if len(query) > 1:
                query += '&'
            query += parse.urlencode(query_params)

        baseurl = parse.urljoin(self.wapi_url, parse.quote(relative_path))
        return baseurl + query

    def _validate_objtype_or_die(self, objtype):
        if not objtype:
            raise ValueError('WAPI object type can\'t be empty.')
        if '/' in objtype:
            raise ValueError('WAPI object type can\'t contains slash.')

    def get_object(self, objtype, payload=None, return_fields=None,
                   extattrs=None):
        """Retrieve a list of Infoblox objects of type 'objtype'

        Args:
            objtype  (str): Infoblox object type, e.g. 'view', 'tsig', etc.
            payload (dict): Payload with data to send
        Returns:
            A list of the Infoblox objects requested
        Raises:
            InfobloxObjectNotFound
        """
        if return_fields is None:
            return_fields = []
        if extattrs is None:
            extattrs = {}

        self._validate_objtype_or_die(objtype)

        query_params = dict()
        if return_fields:
            query_params['_return_fields'] = ','.join(return_fields)

        headers = {'Content-type': 'application/json'}

        data = jsonutils.dump_as_bytes(payload)
        url = self._construct_url(objtype, query_params, extattrs)

        r = self.session.get(url,
                             data=data,
                             verify=self.sslverify,
                             headers=headers)

        if r.status_code != requests.codes.ok:
            raise exc.InfobloxSearchError(
                response=jsonutils.loads(r.content),
                objtype=objtype,
                content=r.content,
                code=r.status_code)

        return jsonutils.loads(r.content)

    def create_object(self, objtype, payload, return_fields=None):
        """Create an Infoblox object of type 'objtype'

        Args:
            objtype  (str): Infoblox object type, e.g. 'network', 'range', etc.
            payload (dict): Payload with data to send
        Returns:
            The object reference of the newly create object
        Raises:
            InfobloxException
        """
        if not return_fields:
            return_fields = []

        self._validate_objtype_or_die(objtype)

        query_params = dict()

        if return_fields:
            query_params['_return_fields'] = ','.join(return_fields)

        url = self._construct_url(objtype, query_params)

        headers = {'Content-type': 'application/json'}

        r = self.session.post(url,
                              data=jsonutils.dump_as_bytes(payload),
                              verify=self.sslverify,
                              headers=headers)

        if r.status_code != requests.codes.CREATED:
            raise exc.InfobloxCannotCreateObject(
                response=jsonutils.loads(r.content),
                objtype=objtype,
                content=r.content,
                args=payload,
                code=r.status_code)

        return jsonutils.loads(r.content)

    def call_func(self, func_name, ref, payload, return_fields=None):
        if not return_fields:
            return_fields = []

        query_params = dict()
        query_params['_function'] = func_name

        if return_fields:
            query_params['_return_fields'] = ','.join(return_fields)

        url = self._construct_url(ref, query_params)

        headers = {'Content-type': 'application/json'}
        r = self.session.post(url,
                              data=jsonutils.dump_as_bytes(payload),
                              verify=self.sslverify,
                              headers=headers)

        if r.status_code not in (requests.codes.CREATED,
                                 requests.codes.ok):
            raise exc.InfobloxFuncException(
                response=jsonutils.loads(r.content),
                ref=ref,
                func_name=func_name,
                content=r.content,
                code=r.status_code)

        return jsonutils.loads(r.content)

    def update_object(self, ref, payload):
        """Update an Infoblox object

        Args:
            ref      (str): Infoblox object reference
            payload (dict): Payload with data to send
        Returns:
            The object reference of the updated object
        Raises:
            InfobloxException
        """

        headers = {'Content-type': 'application/json'}
        r = self.session.put(self._construct_url(ref),
                             data=jsonutils.dump_as_bytes(payload),
                             verify=self.sslverify,
                             headers=headers)

        if r.status_code != requests.codes.ok:
            raise exc.InfobloxCannotUpdateObject(
                response=jsonutils.loads(r.content),
                ref=ref,
                content=r.content,
                code=r.status_code)

        return jsonutils.loads(r.content)

    def delete_object(self, ref):
        """Remove an Infoblox object

        Args:
            ref      (str): Object reference
        Returns:
            The object reference of the removed object
        Raises:
            InfobloxException
        """
        r = self.session.delete(self._construct_url(ref),
                                verify=self.sslverify)

        if r.status_code != requests.codes.ok:
            raise exc.InfobloxCannotDeleteObject(
                response=jsonutils.loads(r.content),
                ref=ref,
                content=r.content,
                code=r.status_code)

        return jsonutils.loads(r.content)
