import json
import logging
import re
import ssl
import sys
import urllib2


class _NullHandler(logging.Handler):

    """
    Null Handler class for Logger
    """

    def emit(self, record):
        pass

logger = logging.getLogger(__name__)
logger.addHandler(_NullHandler())


class ZabbixAPIException(Exception):

    """
    ZabbixAPI exception class

    code list:
    -32602 - Invalid params (eg already exists)
    -32500 - no permissions
    """
    pass


class ZabbixAPIObjectClass(object):

    """
    ZabbixAPI Object class
    """

    def __init__(self, name, parent):
        self.name = name
        self.parent = parent

    def __getattr__(self, attr):
        """
        Dynamically create a method (ie: get)
        """

        def fn(*args, **kwargs):
            if args and kwargs:
                raise TypeError("Found both args and kwargs")

            logger.debug(attr)

            return self.parent.do_request(
                '{0}.{1}'.format(self.name, attr),
                args or kwargs
            )['result']

        return fn


class ZabbixAPI(object):

    """
    ZabbixAPI class, implement interface to zabbix api

    Examples:
      z = ZabbixAPI('https://zabbix.server', user='admin', password='zabbix')

      # Get API Version
      z.api_info.version()
      >> u'2.2.1'
      # ir
      z.do_request('apiinfo.version')
      >> {u'jsonrpc': u'2.0', u'result': u'2.2.1', u'id': u'1'}

      # Get all disabled hosts
      z.host.getobjects(status=1)
      # or
      z.do_request('host.getobjects', {'status':1})

    """

    def __init__(self, url='https://localhost/zabbix',
                 use_authenticate=False, user='admin', password='zabbix'):
        self.use_authenticate = use_authenticate
        self.auth = None
        self.url = url + '/api_jsonrpc.php'
        self.__login(user, password)
        logger.debug("JSON-PRC Server: %s", self.url)

    def __getattr__(self, attr):
        """
        Dynamically create an object class (ie: host)
        """
        return ZabbixAPIObjectClass(attr, self)

    def __login(self, user='', password=''):
        """
        Do login to zabbix server

        Attributes:
          user (str):     Zabbix user
          password (str): Zabbix user password
        """
        logger.debug("ZabbixAPI.login({0},{1})".format(user, password))

        self.auth = None

        if self.use_authenticate:
            self.auth = self.user.authenticate(user=user, password=password)
        else:
            self.auth = self.user.login(user=user, password=password)

    def api_version(self):
        """
        Return version of Zabbix API
        """
        return self.apiinfo.version()

    def do_request(self, method, params=None):
        """
        Make request to Zabbix API

        Attributes:
          method (str): Any of ZabbixAPI method, like: apiinfo.version
          params (str): Methods parameters

        Examples:
          z = ZabbixAPI()
          apiinfo = z.do_request('apiinfo.version')
        """
        request_json = {
            'jsonrpc': '2.0',
            'method': method,
            'params': params or {},
            'auth': self.auth,
            'id': '1',
        }

        if sys.version_info[0:3] >= (2, 7, 9):
                # Create default context to skip SSL cert verification.
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
        else:
            ctx = None

        logger.debug(
            'urllib2.Request({0}, {1})'.format(
                self.url,
                json.dumps(request_json)))
        req = urllib2.Request(self.url, json.dumps(request_json))
        req.get_method = lambda: 'POST'
        req.add_header('Content-Type', 'application/json-rpc')

        try:
            if sys.version_info[0:3] >= (2, 7, 9):
                res = urllib2.urlopen(req, context=ctx)
            else:
                res = urllib2.urlopen(req)

            response_json = json.load(res)
        except ValueError:
            raise ZabbixAPIException("Unable to parse json: %" % res)

        logger.debug("Response Body: %s" % json.dumps(response_json, indent=4,
                                                      separators=(',', ': ')))

        if 'error' in response_json:
            msg = "Error {code}: {message}, {data} while sending {json}".format(
                code=response_json['error']['code'],
                message=response_json['error']['message'],
                data=response_json['error']['data'],
                json=str(request_json))
            raise ZabbixAPIException(msg, response_json['error']['code'])

        return response_json

    def get_id(self, item_type, item=None, with_id=False, hostid=None, **args):
        """
        Return id or ids of zabbix objects.

        Attributes:
          item_type   (str): Type of zabbix object
          item        (str): Name of zabbix object.
                             If None - will return list of all object in the scope.
          with_id    (bool): Return values will be in zabbix format.
                             Examlpe: {'itemid: 128}
          name       (bool): Return name instead id.
          hostid      (int): Specify id of host for special cases
          templateids (int): Specify scope to specific template
          app_name    (str): Specify scope to specific template

        """
        result = None
        name = args.get('name', False)

        type_ = '{item_type}.get'.format(item_type=item_type)

        item_filter_name = {
            'trigger': 'description',
            'triggerprototype': 'description',
            'mediatype': 'description',
            'user': 'alias',
            'usermacro': 'macro',
        }

        item_id_name = {
            'usermacro': 'hostmacro',
            'usergroup': 'usrgrp',
            'hostgroup': 'group',
            'discoveryrule': 'item',
            'graphprototype': 'graph',
            'itemprototype': 'item',
            'triggerprototype': 'trigger',
        }

        filter_ = {
            'filter': {
                    item_filter_name.get(item_type, 'name'): item,
                },
            'output': 'extend'}
        if hostid:
            filter_['filter'].update({'hostid': hostid})

        if args.get('templateids'):
            if item_type == 'usermacro':
                filter_['hostids'] = args['templateids']
            else:
                filter_['templateids'] = args['templateids']
        if args.get('app_name'):
            filter_['application'] = args['app_name']

        logger.debug(
            'do_request( "{type}", {filter} )'.format(
                type=type_,
                filter=filter_))
        response = self.do_request(type_, filter_)['result']

        if response:
            item_id = '{item}id'.format(
                item=item_id_name.get(
                    item_type,
                    item_type))
            result = []
            for obj in response:
                # Check if object not belong current template
                if args.get('templateids'):
                    if not obj.get('templateid') in ("0", None) or not len(obj.get('templateids',[])) == 0:
                            continue

                if name:
                    result.append(obj.get(item_filter_name.get(item_type, 'name')))
                elif with_id:
                    result.append({item_id: int(obj.get(item_id))})
                else:
                    result.append(int(obj.get(item_id)))

            list_types = (list, type(None))
            if not isinstance(item, list_types):
                result = result[0]

        return result
