#!/usr/bin/env python

#   Copyright 2015 Red Hat 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.
#
#  Purpose: An ansible module to communicate with zabbix.
#

import json
import httplib2
import sys
import os
import re

class ZabbixAPI(object):
    '''
        ZabbixAPI class
    '''
    classes = {
        'Action': ['create', 'delete', 'get', 'update'],
        'Alert': ['get'],
        'Application': ['create', 'delete', 'get', 'massadd', 'update'],
        'Configuration': ['export', 'import'],
        'Dcheck': ['get'],
        'Dhost': ['get'],
        'Drule': ['copy', 'create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Dservice': ['get'],
        'Event': ['acknowledge', 'get'],
        'Graph': ['create', 'delete', 'get', 'update'],
        'Graphitem': ['get'],
        'Graphprototype': ['create', 'delete', 'get', 'update'],
        'History': ['get'],
        'Hostgroup': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'massadd', 'massremove', 'massupdate', 'update'],
        'Hostinterface': ['create', 'delete', 'get', 'massadd', 'massremove', 'replacehostinterfaces', 'update'],
        'Host': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'massadd', 'massremove', 'massupdate', 'update'],
        'Hostprototype': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Httptest': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Iconmap': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Image': ['create', 'delete', 'get', 'update'],
        'Item': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Itemprototype': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Maintenance': ['create', 'delete', 'get', 'update'],
        'Map': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Mediatype': ['create', 'delete', 'get', 'update'],
        'Proxy': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Screen': ['create', 'delete', 'get', 'update'],
        'Screenitem': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'update', 'updatebyposition'],
        'Script': ['create', 'delete', 'execute', 'get', 'getscriptsbyhosts', 'update'],
        'Service': ['adddependencies', 'addtimes', 'create', 'delete', 'deletedependencies', 'deletetimes', 'get', 'getsla', 'isreadable', 'iswritable', 'update'],
        'Template': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'massadd', 'massremove', 'massupdate', 'update'],
        'Templatescreen': ['copy', 'create', 'delete', 'get', 'isreadable', 'iswritable', 'update'],
        'Templatescreenitem': ['get'],
        'Trigger': ['adddependencies', 'create', 'delete', 'deletedependencies', 'get', 'isreadable', 'iswritable', 'update'],
        'Triggerprototype': ['create', 'delete', 'get', 'update'],
        'User': ['addmedia', 'create', 'delete', 'deletemedia', 'get', 'isreadable', 'iswritable', 'login', 'logout', 'update', 'updatemedia', 'updateprofile'],
        'Usergroup': ['create', 'delete', 'get', 'isreadable', 'iswritable', 'massadd', 'massupdate', 'update'],
        'Usermacro': ['create', 'createglobal', 'delete', 'deleteglobal', 'get', 'update', 'updateglobal'],
        'Usermedia': ['get'],
    }

    def __init__(self, data={}):
        self.server = data['server'] or None
        self.username = data['user'] or None
        self.password = data['password'] or None
        if any(map(lambda value: value == None, [self.server, self.username, self.password])):
            print 'Please specify zabbix server url, username, and password.'
            sys.exit(1)

        self.verbose = data.has_key('verbose')
        self.use_ssl = data.has_key('use_ssl')
        self.auth = None

        for class_name, method_names in self.classes.items():
            #obj = getattr(self, class_name)(self)
            #obj.__dict__
            setattr(self, class_name.lower(), getattr(self, class_name)(self))

        results = self.user.login(user=self.username, password=self.password)

        if results[0]['status'] == '200':
            if results[1].has_key('result'):
                self.auth = results[1]['result']
            elif results[1].has_key('error'):
                print "Unable to authenticate with zabbix server. {0} ".format(results[1]['error'])
                sys.exit(1)
        else:
            print "Error in call to zabbix. Http status: {0}.".format(results[0]['status'])
            sys.exit(1)

    def perform(self, method, params):
        '''
        This method calls your zabbix server.

        It requires the following parameters in order for a proper request to be processed:

            jsonrpc - the version of the JSON-RPC protocol used by the API; the Zabbix API implements JSON-RPC version 2.0;
            method - the API method being called;
            params - parameters that will be passed to the API method;
            id - an arbitrary identifier of the request;
            auth - a user authentication token; since we don't have one yet, it's set to null.
        '''
        http_method = "POST"
        if params.has_key("http_method"):
            http_method = params['http_method']

        jsonrpc = "2.0"
        if params.has_key('jsonrpc'):
            jsonrpc = params['jsonrpc']

        rid = 1
        if params.has_key('id'):
            rid = params['id']

        http = None
        if self.use_ssl:
            http = httplib2.Http()
        else:
            http = httplib2.Http( disable_ssl_certificate_validation=True,)

        headers = params.get('headers', {})
        headers["Content-type"] = "application/json"

        body = {
            "jsonrpc": jsonrpc,
            "method":  method,
            "params":  params,
            "id":      rid,
            'auth':    self.auth,
        }

        if method in ['user.login','api.version']:
            del body['auth']

        body = json.dumps(body)

        if self.verbose:
            print body
            print method
            print headers
            httplib2.debuglevel = 1

        response, results = http.request(self.server, http_method, body, headers)

        if self.verbose:
            print response
            print results

        try:
            results = json.loads(results)
        except ValueError as e:
            results = {"error": e.message}

        return response, results

    '''
    This bit of metaprogramming is where the ZabbixAPI subclasses are created.
    For each of ZabbixAPI.classes we create a class from the key and methods
    from the ZabbixAPI.classes values.  We pass a reference to ZabbixAPI class
    to each subclass in order for each to be able to call the perform method.
    '''
    @staticmethod
    def meta(class_name, method_names):
        # This meta method allows a class to add methods to it.
        def meta_method(Class, method_name):
            # This template method is a stub method for each of the subclass
            # methods.
            def template_method(self, **params):
                return self.parent.perform(class_name.lower()+"."+method_name, params)
            template_method.__doc__ = "https://www.zabbix.com/documentation/2.4/manual/api/reference/%s/%s" % (class_name.lower(), method_name)
            template_method.__name__ = method_name
            # this is where the template method is placed inside of the subclass
            # e.g. setattr(User, "create", stub_method)
            setattr(Class, template_method.__name__, template_method)

        # This class call instantiates a subclass. e.g. User
        Class=type(class_name, (object,), { '__doc__': "https://www.zabbix.com/documentation/2.4/manual/api/reference/%s" % class_name.lower() })
        # This init method gets placed inside of the Class 
        # to allow it to be instantiated.  A reference to the parent class(ZabbixAPI)
        # is passed in to allow each class access to the perform method.
        def __init__(self, parent):
            self.parent = parent
        # This attaches the init to the subclass. e.g. Create
        setattr(Class, __init__.__name__, __init__)
        # For each of our ZabbixAPI.classes dict values
        # Create a method and attach it to our subclass.
        # e.g.  'User': ['delete', 'get', 'updatemedia', 'updateprofile',
        #                'update', 'iswritable', 'logout', 'addmedia', 'create',
        #                'login', 'deletemedia', 'isreadable'],
        # User.delete
        # User.get
        for method_name in method_names:
            meta_method(Class, method_name)
        # Return our subclass with all methods attached
        return Class

# Attach all ZabbixAPI.classes to ZabbixAPI class through metaprogramming
for class_name, method_names in ZabbixAPI.classes.items():
    setattr(ZabbixAPI, class_name, ZabbixAPI.meta(class_name, method_names))

def main():

    module = AnsibleModule(
        argument_spec = dict(
            server=dict(default='https://localhost/zabbix/api_jsonrpc.php', type='str'),
            user=dict(default=None, type='str'),
            password=dict(default=None, type='str'),
            zbx_class=dict(choices=ZabbixAPI.classes.keys()),
            action=dict(default=None, type='str'),
            params=dict(),
            debug=dict(default=False, type='bool'),
        ),
        #supports_check_mode=True
    )

    user = module.params.get('user', None)
    if not user:
        user = os.environ['ZABBIX_USER']

    pw = module.params.get('password', None)
    if not pw:
        pw = os.environ['ZABBIX_PASSWORD']

    server = module.params['server']

    if module.params['debug']:
        options['debug'] = True

    api_data = {
        'user': user,
        'password': pw,
        'server': server,
    }

    if not user or not pw or not server:
        module.fail_json('Please specify the user, password, and the zabbix server.')

    zapi = ZabbixAPI(api_data)

    zbx_class = module.params.get('zbx_class')
    action = module.params.get('action')
    params = module.params.get('params', {})


    # Get the instance we are trying to call
    zbx_class_inst = zapi.__getattribute__(zbx_class.lower())
    # Get the instance's method we are trying to call
    zbx_action_method = zapi.__getattribute__(zbx_class.capitalize()).__dict__[action]
    # Make the call with the incoming params
    results = zbx_action_method(zbx_class_inst, **params)

    # Results Section
    changed_state = False
    status = results[0]['status']
    if status not in ['200', '201']:
        #changed_state = False
        module.fail_json(msg="Http response: [%s] - Error: %s" % (str(results[0]), results[1]))

    module.exit_json(**{'results': results[1]['result']})

from ansible.module_utils.basic import *

main()