#!/usr/bin/python2.7
"""
Copyright (c) 2014, ICFLIX Media FZ LLC All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.

Desc: Generate Nagios configuration from given file, resp. from check_multi.
"""
import logging
import logging.handlers
import json
import os.path
import re
import sys
import yaml

from nagios_to_yaml import NagiosToYaml

ICINGA_DIR = '/etc/icinga'
LOG_FORMAT = '%(asctime)s %(levelname)-10s %(message)s'

MACHINEDB_FILE = '/etc/icinga/machines.json'
NAGIOS_DEFS_FILE = '/etc/icinga/nagios.yml'
NAGIOS_TKEYS = [
    'commands',
    'contacts',
    'contactgroups',
    'datacenters',
    'hostgroups',
    'hosts',
    'services'
]

STAGING_DOMAIN = 'icflix.io'
# 7 minutes
SVC_FRESHNESS_THRESHOLD = 420

# Make sure Nagios knows all Hosts in MachineDB
#  -> <FQDN>.cfg must exist for each and every Host in MDB
# Re-Generate hostgroups
# Re-Generate contacts
# Re-Generate contactsgroups
# Re-Generate commands
class NagiosConfigGenerator(object):
    """Generate Nagios Configuration for *ONE* Host from given file."""

    def __init__(self):
        self.machine_db = None
        self.nagios_db = None
        self.load_machine_db()
        self.load_nagios_definitions()
        self.mdb_to_nagios()

    def add_datacenter_to_nagios(self, dct_dict):
        """Add given Datacenter to Nagios. If given Datacenter is already
        known, merge in attributes/values, but don't over-ride Nagios ones.
        """
        nagios_dcs = self.nagios_db['datacenters']
        dct_name = dct_dict.pop('host_name')
        if dct_name not in nagios_dcs:
            nagios_dcs[dct_name] = {}

        nagios_dct = nagios_dcs[dct_name]
        if 'hostgroups' not in nagios_dct:
            nagios_dct['hostgroups'] = list()

        nagios_dct['hostgroups'].append('datacenter')
        if 'host' not in nagios_dct:
            nagios_dct['host'] = {}

        for attr in dct_dict.iterkeys():
            if attr in nagios_dct['host']:
                # Don't over-ride Nagios definitions
                continue

            nagios_dct['host'][attr] = dct_dict[attr]

    def add_host_to_nagios(self, host_dict, is_lxc):
        """Add given Host to Nagios. If given Host is already known, merge in
        values, but don't over-ride Nagios ones.
        """
        nagios_hosts = self.nagios_db['hosts']
        hostname = host_dict.pop('host_name')
        if hostname not in nagios_hosts:
            nagios_hosts[hostname] = {}

        nagios_host = nagios_hosts[hostname]
        if 'hostgroups' not in nagios_host:
            nagios_host['hostgroups'] = list()

        auto_hostgroup = self.get_auto_hostgroup(hostname)
        nagios_host['hostgroups'].append(auto_hostgroup)
        if is_lxc:
            nagios_host['hostgroups'].append('lxc')

        if ('_DOMAIN' in host_dict
                and host_dict['_DOMAIN'] == STAGING_DOMAIN):
            nagios_host['hostgroups'].append('stage')

        if 'host' not in nagios_host:
            nagios_host['host'] = {}

        for attr in host_dict.iterkeys():
            if attr in nagios_host['host']:
                # Don't over-ride Nagios definitions
                continue

            nagios_host['host'][attr] = host_dict[attr]

    def add_services_to_host(self, nagios_host, ext_svcs):
        """Add (external) service definition to Nagios."""
        if 'services' not in nagios_host:
            nagios_host['services'] = {}

        nagios_svcs = nagios_host['services']
        for svc_key in ext_svcs['services'].iterkeys():
            if svc_key not in nagios_svcs:
                nagios_svcs[svc_key] = {}

            nagios_svc = nagios_svcs[svc_key]
            for attr in ext_svcs['services'][svc_key].iterkeys():
                if attr in nagios_svc:
                    continue

                nagios_svc[attr] = ext_svcs['services'][svc_key][attr]

    def ensure_host_definitions(self):
        """Ensure Nagios knows all Hosts defined in MDB. This is required in
        order to re-generate Hostgroups, because it could easilly happen Nagios
        wouldn't know Host(s) in hostgroups.
        """
        for host_key in self.nagios_db['hosts'].iterkeys():
            host_dict = self.nagios_db['hosts'][host_key]
            host_dict['host']['host_name'] = host_key
            self.ensure_host_definition(host_dict)

    def ensure_host_definition(self, host_dict):
        """Ensure file with Host definition exists."""
        if host_dict is None:
            return (-1)

        host_file = ('%s/objects/host_%s.cfg' %
                     (ICINGA_DIR, host_dict['host']['host_name']))
        if os.path.exists(host_file):
            #logging.debug("File '%s' exists.", host_file)
            return 1

        fhandle = open(host_file, 'w+')
        self.write_definition(fhandle, 'host', host_dict['host'])

        if 'services' not in host_dict:
            host_dict['services'] = {}

        dummy_svc = dict()
        dummy_svc['active_checks_enabled'] = 1
        dummy_svc['check_command'] = 'return-ok'
        dummy_svc['check_interval'] = 20
        dummy_svc['host_name'] = host_dict['host']['host_name']
        dummy_svc['use'] = 'generic-service'
        host_dict['services']['dummy-ok'] = dummy_svc

        for service_key in host_dict['services'].iterkeys():
            service_copy = host_dict['services'][service_key]
            service_copy['service_description'] = service_key
            self.write_definition(fhandle, 'service',
                                  service_copy)
            del service_copy

        fhandle.close()
        return 0

    def finish_host_definition(self, host_dict, hostname):
        """Add/over-ride attributes in Host definition."""
        if hostname not in self.nagios_db['hosts']:
            return

        if 'host' not in self.nagios_db['hosts'][hostname]:
            return

        for attr in self.nagios_db['hosts'][hostname]['host'].iterkeys():
            host_dict[attr] = self.nagios_db['hosts'][hostname]['host'][attr]

    def get_auto_hostgroup(self, hostname):
        """Determine automatic Nagios hostgroup."""
        auto_hostgroup = hostname.split('.')[0]
        auto_hostgroup = re.sub(r'(\d+$|\d+[a-z]+)$', r'', auto_hostgroup)
        return auto_hostgroup

    def get_host_dict(self, hostname, machine_ip, ssh_port, parents):
        """Create Nagios 'host' as a dictionary from given params.
        Parents is expected to be either None or a list.
        """
        host_dict = {}
        host_dict['use'] = 'generic-host'
        host_dict['host_name'] = hostname
        host_dict['address'] = machine_ip
        if parents is not None:
            host_dict['parents'] = ','.join(parents)

        if ssh_port is not None:
            host_dict['_SSH_PORT'] = ssh_port

        splitted = hostname.split('.')
        host_dict['_SHORTNAME'] = '.'.join(splitted[:len(splitted)-2])
        host_dict['_DOMAIN'] = '.'.join(splitted[len(splitted)-2:])
        return host_dict

    def get_padding(self, padding_len):
        """Return padding :)"""
        padding = ''
        while padding_len > 0:
            padding += ' '
            padding_len -= 1

        return padding

    def get_ssh_port(self, machine_obj, is_lxc):
        """Determine SSH port for given Machine."""
        ssh_port = 22
        if is_lxc == False:
            return ssh_port

        if 'ports' not in machine_obj:
            # Ehm, this is a bit inconclusive, isn't it?
            return ssh_port

        for port_cfg in machine_obj['ports']:
            # dict is expected here
            if 'private_port' not in port_cfg:
                continue

            if int(port_cfg['private_port']) == 22:
                ssh_port = int(port_cfg['public_port'])

        return ssh_port

    def load_machine_db(self):
        """Just loads machine DB from JSON."""
        with open(MACHINEDB_FILE, 'r') as fhandle:
            self.machine_db = json.load(fhandle)['machines']

    def load_nagios_definitions(self):
        """Load Nagios definitions from YAML."""
        with open(NAGIOS_DEFS_FILE, 'r') as fhandle:
            self.nagios_db = yaml.load(fhandle)

        # Make nagios_db sane
        for top_key in NAGIOS_TKEYS:
            if top_key in self.nagios_db:
                continue

            self.nagios_db[top_key] = {}

        if 'passive' not in self.nagios_db['services']:
            self.nagios_db['services']['passive'] = {}

        if 'active' not in self.nagios_db['services']:
            self.nagios_db['services']['active'] = {}

    def import_config(self, services_cfg):
        """Import configuration file (sent) from remote Host."""
        if not os.path.exists(services_cfg):
            logging.error("Given file '%s' doesn't exist.", services_cfg)
            return False

        hostname = os.path.basename(services_cfg).replace('.cfg', '')
        if hostname == '':
            logging.error('I have empty hostname! :-(')
            return False

        nagios_host = None
        for host_key in self.nagios_db['hosts'].iterkeys():
            if hostname == host_key:
                nagios_host = self.nagios_db['hosts'][host_key]
                break

        if nagios_host is None:
            logging.error('Machine %s not found in Nagios/MDB.', hostname)
            return False

        logging.info('FQDN: %s', hostname)
        logging.info('IP: %s', nagios_host['host']['address'])
        logging.info('SSH: %s', nagios_host['host']['_SSH_PORT'])
        logging.info('Hostgroups: %s', nagios_host['hostgroups'])

        nag2yaml = NagiosToYaml()
        nag2yaml.parse_nagios_config(services_cfg)
        ext_services = nag2yaml.nagios_cfg

        for extsvc_key in ext_services['services'].iterkeys():
            ext_service = ext_services['services'][extsvc_key]
            if 'stage' in nagios_host['hostgroups']:
                ext_service['use'] = 'stage-service'
            else:
                ext_service['use'] = 'generic-service'

            ext_service['check_freshness'] = 1
            ext_service['active_checks_enabled'] = 0
            ext_service['passive_checks_enabled'] = 1
            ext_service['freshness_threshold'] = SVC_FRESHNESS_THRESHOLD
            ext_service['check_command'] = 'check_dummy_4p!2 "check is stale"'
            if extsvc_key not in self.nagios_db['services']['passive']:
                continue

            # Over-ride attributes from ['services']['passive']
            svc_nagios = self.nagios_db['services']['passive'][extsvc_key]
            for attr in svc_nagios.iterkeys():
                ext_service[attr] = svc_nagios[attr]

        self.add_services_to_host(nagios_host, ext_services)
        host_file = '%s/objects/host_%s.cfg' % (ICINGA_DIR, hostname)
        with open(host_file, 'w+') as fhandle:
            host_copy = nagios_host['host'].copy()
            host_copy['host_name'] = hostname
            self.write_definition(fhandle, 'host', host_copy)
            for svc_key in nagios_host['services'].iterkeys():
                service_copy = nagios_host['services'][svc_key].copy()
                service_copy['service_description'] = svc_key
                self.write_definition(fhandle, 'service', service_copy)

        return True

    def mdb_to_nagios(self):
        """Sync Nagios YAML with MDB."""
        for host_key in self.machine_db.iterkeys():
            hostname = '%s.icflix.com' % (host_key)
            mdb_host = self.machine_db[host_key]
            if 'datacenter' in mdb_host and 'provider' in mdb_host:
                dct_name = '%s.%s' % (mdb_host['datacenter'],
                                      mdb_host['provider'])