#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}


DOCUMENTATION = r'''

module: zabbix_maintenance
short_description: Create Zabbix maintenance windows
description:
    - This module will let you create Zabbix maintenance windows.
version_added: "1.8"
author: "Alexander Bulimov (@abulimov)"
requirements:
    - "python >= 2.6"
    - "zabbix-api >= 0.5.4"
options:
    state:
        description:
            - Create or remove a maintenance window. Maintenance window to remove is identified by name.
        default: present
        choices: [ "present", "absent" ]
        type: str
    host_names:
        description:
            - Hosts to manage maintenance window for.
            - B(Required) option when I(state=present) and I(host_groups) is not used.
        aliases: [ "host_name" ]
        type: list
        elements: str
    host_groups:
        description:
            - Host groups to manage maintenance window for.
            - B(Required) option when I(state=present) and I(host_names) is not used.
        aliases: [ "host_group" ]
        type: list
        elements: str
    minutes:
        description:
            - Length of maintenance window in minutes.
        default: 10
        type: int
    name:
        description:
            - Unique name of maintenance window.
        required: true
        type: str
    desc:
        description:
            - Short description of maintenance window.
        default: Created by Ansible
        type: str
    collect_data:
        description:
            - Type of maintenance. With data collection, or without.
        type: bool
        default: 'yes'

extends_documentation_fragment:
    - zabbix

notes:
    - Useful for setting hosts in maintenance mode before big update,
      and removing maintenance window after update.
    - Module creates maintenance window from now() to now() + minutes,
      so if Zabbix server's time and host's time are not synchronized,
      you will get strange results.
    - Install required module with 'pip install zabbix-api' command.
'''

EXAMPLES = r'''
- name: Create a named maintenance window for host www1 for 90 minutes
  zabbix_maintenance:
    name: Update of www1
    host_name: www1.example.com
    state: present
    minutes: 90
    server_url: https://monitoring.example.com
    login_user: ansible
    login_password: pAsSwOrD

- name: Create a named maintenance window for host www1 and host groups Office and Dev
  zabbix_maintenance:
    name: Update of www1
    host_name: www1.example.com
    host_groups:
      - Office
      - Dev
    state: present
    server_url: https://monitoring.example.com
    login_user: ansible
    login_password: pAsSwOrD

- name: Create a named maintenance window for hosts www1 and db1, without data collection.
  zabbix_maintenance:
    name: update
    host_names:
      - www1.example.com
      - db1.example.com
    state: present
    collect_data: False
    server_url: https://monitoring.example.com
    login_user: ansible
    login_password: pAsSwOrD

- name: Remove maintenance window by name
  zabbix_maintenance:
    name: Test1
    state: absent
    server_url: https://monitoring.example.com
    login_user: ansible
    login_password: pAsSwOrD
'''


import atexit
import datetime
import time
import traceback

try:
    from zabbix_api import ZabbixAPI
    HAS_ZABBIX_API = True
except ImportError:
    ZBX_IMP_ERR = traceback.format_exc()
    HAS_ZABBIX_API = False

from ansible.module_utils.basic import AnsibleModule, missing_required_lib


def create_maintenance(zbx, group_ids, host_ids, start_time, maintenance_type, period, name, desc):
    end_time = start_time + period
    try:
        zbx.maintenance.create(
            {
                "groupids": group_ids,
                "hostids": host_ids,
                "name": name,
                "maintenance_type": maintenance_type,
                "active_since": str(start_time),
                "active_till": str(end_time),
                "description": desc,
                "timeperiods": [{
                    "timeperiod_type": "0",
                    "start_date": str(start_time),
                    "period": str(period),
                }]
            }
        )
    # zabbix_api can call sys.exit() so we need to catch SystemExit here
    except (Exception, SystemExit) as e:
        return 1, None, str(e)
    return 0, None, None


def update_maintenance(zbx, maintenance_id, group_ids, host_ids, start_time, maintenance_type, period, desc):
    end_time = start_time + period
    try:
        zbx.maintenance.update(
            {
                "maintenanceid": maintenance_id,
                "groupids": group_ids,
                "hostids": host_ids,
                "maintenance_type": maintenance_type,
                "active_since": str(start_time),
                "active_till": str(end_time),
                "description": desc,
                "timeperiods": [{
                    "timeperiod_type": "0",
                    "start_date": str(start_time),
                    "period": str(period),
                }]
            }
        )
    # zabbix_api can call sys.exit() so we need to catch SystemExit here
    except (Exception, SystemExit) as e:
        return 1, None, str(e)
    return 0, None, None


def get_maintenance(zbx, name):
    try:
        maintenances = zbx.maintenance.get(
            {
                "filter":
                {
                    "name": name,
                },
                "selectGroups": "extend",
                "selectHosts": "extend"
            }
        )
    # zabbix_api can call sys.exit() so we need to catch SystemExit here
    except (Exception, SystemExit) as e:
        return 1, None, str(e)

    for maintenance in maintenances:
        maintenance["groupids"] = [group["groupid"] for group in maintenance["groups"]] if "groups" in maintenance else []
        maintenance["hostids"] = [host["hostid"] for host in maintenance["hosts"]] if "hosts" in maintenance else []
        return 0, maintenance, None

    return 0, None, None


def delete_maintenance(zbx, maintenance_id):
    try:
        zbx.maintenance.delete([maintenance_id])
    # zabbix_api can call sys.exit() so we need to catch SystemExit here
    except (Exception, SystemExit) as e:
        return 1, None, str(e)
    return 0, None, None


def get_group_ids(zbx, host_groups):
    group_ids = []
    for group in host_groups:
        try:
            result = zbx.hostgroup.get(
                {
                    "output": "extend",
                    "filter":
                    {
                        "name": group
                    }
                }
            )
        # zabbix_api can call sys.exit() so we need to catch SystemExit here
        except (Exception, SystemExit) as e:
            return 1, None, str(e)

        if not result:
            return 1, None, "Group id for group %s not found" % group

        group_ids.append(result[0]["groupid"])

    return 0, group_ids, None


def get_host_ids(zbx, host_names):
    host_ids = []
    for host in host_names:
        try:
            result = zbx.host.get(
                {
                    "output": "extend",
                    "filter":
                    {
                        "name": host
                    }
                }
            )
        # zabbix_api can call sys.exit() so we need to catch SystemExit here
        except (Exception, SystemExit) as e:
            return 1, None, str(e)

        if not result:
            return 1, None, "Host id for host %s not found" % host

        host_ids.append(result[0]["hostid"])

    return 0, host_ids, None


def main():
    module = AnsibleModule(
        argument_spec=dict(
            state=dict(type='str', required=False, default='present', choices=['present', 'absent']),
            server_url=dict(type='str', required=True, aliases=['url']),
            host_names=dict(type='list', required=False, default=None, aliases=['host_name']),
            minutes=dict(type='int', required=False, default=10),
            host_groups=dict(type='list', required=False, default=None, aliases=['host_group']),
            login_user=dict(type='str', required=True),
            login_password=dict(type='str', required=True, no_log=True),
            validate_certs=dict(type='bool', required=False, default=True),
            http_login_user=dict(type='str', required=False, default=None),
            http_login_password=dict(type='str', required=False, default=None, no_log=True),
            name=dict(type='str', required=True),
            desc=dict(type='str', required=False, default="Created by Ansible"),
            collect_data=dict(type='bool', required=False, default=True),
            timeout=dict(type='int', default=10),
        ),
        supports_check_mode=True,
    )

    if not HAS_ZABBIX_API:
        module.fail_json(msg=missing_required_lib('zabbix-api', url='https://pypi.org/project/zabbix-api/'), exception=ZBX_IMP_ERR)

    host_names = module.params['host_names']
    host_groups = module.params['host_groups']
    state = module.params['state']
    login_user = module.params['login_user']
    login_password = module.params['login_password']
    http_login_user = module.params['http_login_user']
    http_login_password = module.params['http_login_password']
    validate_certs = module.params['validate_certs']
    minutes = module.params['minutes']
    name = module.params['name']
    desc = module.params['desc']
    server_url = module.params['server_url']
    collect_data = module.params['collect_data']
    timeout = module.params['timeout']

    if collect_data:
        maintenance_type = 0
    else:
        maintenance_type = 1

    try:
        zbx = ZabbixAPI(server_url, timeout=timeout, user=http_login_user, passwd=http_login_password,
                        validate_certs=validate_certs)
        zbx.login(login_user, login_password)
        atexit.register(zbx.logout)
    # zabbix_api can call sys.exit() so we need to catch SystemExit here
    except (Exception, SystemExit) as e:
        module.fail_json(msg="Failed to connect to Zabbix server: %s" % e)

    changed = False

    if state == "present":

        if not host_names and not host_groups:
            module.fail_json(msg="At least one host_name or host_group must be defined for each created maintenance.")

        now = datetime.datetime.now().replace(second=0)
        start_time = time.mktime(now.timetuple())
        period = 60 * int(minutes)  # N * 60 seconds

        if host_groups:
            (rc, group_ids, error) = get_group_ids(zbx, host_groups)
            if rc != 0:
                module.fail_json(msg="Failed to get group_ids: %s" % error)
        else:
            group_ids = []

        if host_names:
            (rc, host_ids, error) = get_host_ids(zbx, host_names)
            if rc != 0:
                module.fail_json(msg="Failed to get host_ids: %s" % error)
        else:
            host_ids = []

        (rc, maintenance, error) = get_maintenance(zbx, name)
        if rc != 0:
            module.fail_json(msg="Failed to check maintenance %s existence: %s" % (name, error))

        if maintenance and (
            sorted(group_ids) != sorted(maintenance["groupids"]) or
            sorted(host_ids) != sorted(maintenance["hostids"]) or
            str(maintenance_type) != maintenance["maintenance_type"] or
            str(int(start_time)) != maintenance["active_since"] or
            str(int(start_time + period)) != maintenance["active_till"]
        ):
            if module.check_mode:
                changed = True
            else:
                (rc, data, error) = update_maintenance(zbx, maintenance["maintenanceid"], group_ids, host_ids, start_time, maintenance_type, period, desc)
                if rc == 0:
                    changed = True
                else:
                    module.fail_json(msg="Failed to update maintenance: %s" % error)

        if not maintenance:
            if module.check_mode:
                changed = True
            else:
                (rc, data, error) = create_maintenance(zbx, group_ids, host_ids, start_time, maintenance_type, period, name, desc)
                if rc == 0:
                    changed = True
                else:
                    module.fail_json(msg="Failed to create maintenance: %s" % error)

    if state == "absent":

        (rc, maintenance, error) = get_maintenance(zbx, name)
        if rc != 0:
            module.fail_json(msg="Failed to check maintenance %s existence: %s" % (name, error))

        if maintenance:
            if module.check_mode:
                changed = True
            else:
                (rc, data, error) = delete_maintenance(zbx, maintenance["maintenanceid"])
                if rc == 0:
                    changed = True
                else:
                    module.fail_json(msg="Failed to remove maintenance: %s" % error)

    module.exit_json(changed=changed)


if __name__ == '__main__':
    main()
