# -*- coding: utf-8 -*-

""" miscellaneous utils classes and function.

 The reason they don't live in in pynag.Utils is that they have more requirements, and they do on occation import
 pynag.Model og pynag.Parsers
"""


from __future__ import absolute_import
import tempfile
import os
import time
import pynag.Parsers.config_parser
import pynag.Parsers.livestatus
import pynag.Model
import six

from pynag.errors import PynagError
from pynag.Utils import bytes2str


class MiscError(PynagError):
    """Base class for errors in this module."""


class SandboxError(MiscError):
    """Raised when FakeEnvironment tries to modify files outside its sandbox."""


class FakeNagiosEnvironment(object):

    """ Creates a fake nagios environment with minimal configs in /tmp/

    Example:
        >>> nagios = FakeNagiosEnvironment()
        >>> nagios.create_minimal_environment()  # Create temporary director with minimal config
        >>> nagios.update_model()  # Update the global variables in pynag.Model
        >>> nagios.configure_livestatus()  # Configure a livestatus socket
        >>> result, stdout, stderr = nagios.start()  # Start up nagios
        >>> config = nagios.get_config()   # Returns config_parser.Config instance
        >>> livestatus = nagios.get_livestatus()  # Returns livestatus.Livestatus instance
        >>> result, stdout, sdterr = nagios.stop()  # Stop nagios
        >>> nagios.terminate()  # Stops nagios and cleans up everything

    """

    def __init__(self, global_config_file=None, p1_file=None, livestatus=False):
        self.global_config_file = global_config_file
        self.p1_file = p1_file
        self._model_is_dirty = False
        self.livestatus_module_path = livestatus
        self.livestatus_object = None
        self.config = None

        self.tempdir = tempfile.mkdtemp('nagios-environment')
        path_to_socket = os.path.join(self.tempdir, "livestatus.socket")
        self.livestatus_socket_path = path_to_socket

    def get_config(self):
        if not self.config:
            self.create_minimal_environment()
        return self.config

    def get_livestatus(self):
        if not self.livestatus_object:
            self.configure_livestatus()
        return self.livestatus_object

    def update_model(self):
        """ Update the global variables in pynag.Model to point to our config """
        self.original_objects_dir = pynag.Model.pynag_directory
        self.original_cfg_file = pynag.Model.cfg_file
        self.original_config = pynag.Model.config

        pynag.Model.config = self.get_config()
        pynag.Model.cfg_file = self.config.cfg_file
        pynag.Model.pynag_directory = self.objects_dir
        pynag.Model.eventhandlers = []

        self._model_is_dirty = True

    def open_decorator(self, func):
        """ Safety decorator aroundaround self.config.open()

         It wraps around self.config.open() and raises error if the fake
         nagios environment tries to open file outside the sandbox.
        """
        def wrap(filename, *args, **kwargs):
            if filename.startswith(self.tempdir):
                return func(filename, *args, **kwargs)
            else:
                raise SandboxError("FakeNagiosEnvironment tried to open file outside its sandbox: %s" % (filename, ))
        wrap.__name__ = func.__name__
        wrap.__module__ = func.__module__
        return wrap

    def restore_model(self):
        """ Restores the global variables in pynag.Model """
        pynag.Model.config = self.original_config
        pynag.Model.cfg_file = self.original_cfg_file
        pynag.Model.pynag_directory = self.original_objects_dir
        self._model_is_dirty = False

    def create_minimal_environment(self):
        """ Starts a nagios server with empty config in an isolated environment """
        t = self.tempdir
        cfg_file = self.cfg_file = os.path.join(t, "nagios.cfg")
        open(cfg_file, 'w').write('')

        objects_dir = self.objects_dir = os.path.join(t, "conf.d")
        os.mkdir(objects_dir)

        check_result_path = os.path.join(self.tempdir, 'checkresults')
        os.mkdir(check_result_path)

        log_dir = os.path.join(self.tempdir, 'log')
        archive_dir = os.path.join(log_dir, 'archive')
        os.mkdir(log_dir)
        os.mkdir(archive_dir)

        with open(objects_dir + "/minimal_config.cfg", 'w') as f:
            f.write(minimal_config)

        config = self.config = pynag.Parsers.config_parser.Config(cfg_file=cfg_file)
        self.config.open = self.open_decorator(self.config.open)
        config.parse()
        config._edit_static_file(attribute='log_archive_path',
                                 new_value=os.path.join(t, "log/archive"))
        config._edit_static_file(attribute='log_file',
                                 new_value=os.path.join(t, "log/nagios.log"))
        config._edit_static_file(attribute='object_cache_file',
                                 new_value=os.path.join(t, "objects.cache"))
        config._edit_static_file(attribute='precached_object_file',
                                 new_value=os.path.join(t, "/objects.precache"))
        config._edit_static_file(attribute='lock_file',
                                 new_value=os.path.join(t, "nagios.pid"))
        config._edit_static_file(attribute='command_file',
                                 new_value=os.path.join(t, "nagios.cmd"))
        config._edit_static_file(attribute='state_retention_file',
                                 new_value=os.path.join(t, "retention.dat"))
        config._edit_static_file(attribute='status_file',
                                 new_value=os.path.join(t, "status.dat"))
        config._edit_static_file(attribute='cfg_dir', new_value=objects_dir)
        config._edit_static_file(attribute='log_initial_states',
                                 new_value="1")
        config._edit_static_file(attribute='enable_embedded_perl',
                                 new_value='0')
        config._edit_static_file(attribute='event_broker_options',
                                 new_value='-1')
        config._edit_static_file(attribute='illegal_macro_output_chars',
                                 new_value='''~$&|<>''')
        config._edit_static_file(attribute='check_result_path',
                                 new_value=check_result_path)
        config._edit_static_file(attribute='temp_path', new_value=log_dir)
        config._edit_static_file(attribute='temp_file',
                                 new_value=os.path.join(t, "nagios.tmp"))

    def clean_up(self):
        """ Clean up all temporary directories """
        command = ['rm', '-rf', self.tempdir]
        pynag.Utils.runCommand(command=command, shell=False)

    def terminate(self):
        """ Stop the nagios environment and remove all temporary files """
        self.stop()
        if self._model_is_dirty:
            self.restore_model()
        self.clean_up()

    def start(self, start_command=None, timeout=10):
        self.configure_p1_file()
        start_command = bytes2str(start_command)
        if not start_command:
            nagios_binary = self.config.guess_nagios_binary()
            start_command = "%s -d %s" % (nagios_binary, self.config.cfg_file)
        result = pynag.Utils.runCommand(command=start_command)
        code, stdout, stderr = result

        pid_file = os.path.join(self.tempdir, "nagios.pid")
        while not os.path.exists(pid_file) and timeout:
            timeout -= 1
            time.sleep(1)

        start_error = None
        if not os.path.exists(pid_file):
            start_error = "Nagios pid file did not materialize"
        if result[0] != 0:
            start_error = "Nagios did not start, bad return code"

        if start_error:
            if os.path.exists(os.path.join(self.tempdir, "nagios.log")):
                log_file_output = open(os.path.join(self.tempdir, "nagios.log")).read()
            else:
                log_file_output = "No log file found."
            message = start_error
            message += "Command: {start_command}\n"
            message += "Exit Code: {code}\n"
            message += "============\nStandard out\n{stdout}\n"
            message += "=============\nStandard Err\n{stderr}\n"
            message += "=============\nLog File output\n{log_file_output}\n"
            message = message.format(**locals())
            raise Exception(message)
        return result

    def stop(self, stop_command=None):
        pid_file = os.path.join(self.tempdir, "nagios.pid")
        if not os.path.exists(pid_file):
            return
        pid = open(pid_file).read()

        if not stop_command:
            stop_command = "kill -9 %s" % pid
        result = pynag.Utils.runCommand(stop_command)
        time.sleep(0.5)
        return result

    def configure_livestatus(self):
        if not self.livestatus_module_path:
            self.livestatus_module_path = self.guess_livestatus_path()
        line = "%s %s" % (self.livestatus_module_path, self.livestatus_socket_path)
        config = self.get_config()
        config._edit_static_file(attribute="broker_module", new_value=line)
        self.livestatus_object = pynag.Parsers.livestatus.Livestatus(
            nagios_cfg_file=config.cfg_file,
        )

    def guess_p1_file(self):
        global_config = pynag.Parsers.config_parser.Config(cfg_file=self.global_config_file)
        global_config.parse_maincfg()
        for k, v in global_config.maincfg_values:
            if k == 'p1_file':
                return v

    def configure_p1_file(self):
        if not self.p1_file:
            self.p1_file = self.guess_p1_file()
        config = self.get_config()
        config._edit_static_file(attribute='p1_file', new_value=self.p1_file)

    def guess_livestatus_path(self):
        """ Tries to guess a path to mk-livestatus

        Returns:
            string containing full path to mk-livestatus broker_module
        """
        # Find mk_livestatus broker module
        global_config = pynag.Parsers.config_parser.Config(cfg_file=self.global_config_file)
        global_config.parse_maincfg()
        for k, v in global_config.maincfg_values:
            if k == 'broker_module' and 'livestatus' in v:
                livestatus_module = v.split()[0]
                return livestatus_module

    def import_config(self, path):
        """ Copies any file or directory into our environment and include it in object configuration

        Args:
            path:   full path to a nagios cfg file or a directory of cfg files

        Raises:
            Exception if path is not found
        """
        destination = self.objects_dir
        command = "cp -r '{path}' '{destination}/'".format(**locals())
        return os.system(command)


minimal_config = r"""
    define timeperiod {
      alias                          24 Hours A Day, 7 Days A Week
      friday          00:00-24:00
      monday          00:00-24:00
      saturday        00:00-24:00
      sunday          00:00-24:00
      thursday        00:00-24:00
      timeperiod_name                24x7
      tuesday         00:00-24:00
      wednesday       00:00-24:00
    }

    define timeperiod {
      alias                          24x7 Sans Holidays
      friday          00:00-24:00
      monday          00:00-24:00
      saturday        00:00-24:00
      sunday          00:00-24:00
      thursday        00:00-24:00
      timeperiod_name                24x7_sans_holidays
      tuesday         00:00-24:00
      use		us-holidays		; Get holiday exceptions from other timeperiod
      wednesday       00:00-24:00
    }

    define contactgroup {
      alias                          Nagios Administrators
      contactgroup_name              admins
      members                        nagiosadmin
    }

    define command {
      command_line                   $USER1$/check_ping -H $HOSTADDRESS$ -w 3000.0,80% -c 5000.0,100% -p 5
      command_name                   check-host-alive
    }

    define command {
      command_line                   $USER1$/check_dhcp $ARG1$
      command_name                   check_dhcp
    }

    define command {
      command_line                   $USER1$/check_ftp -H $HOSTADDRESS$ $ARG1$
      command_name                   check_ftp
    }

    define command {
      command_line                   $USER1$/check_hpjd -H $HOSTADDRESS$ $ARG1$
      command_name                   check_hpjd
    }

    define command {
      command_line                   $USER1$/check_http -I $HOSTADDRESS$ $ARG1$
      command_name                   check_http
    }

    define command {
      command_line                   $USER1$/check_imap -H $HOSTADDRESS$ $ARG1$
      command_name                   check_imap
    }

    define command {
      command_line                   $USER1$/check_disk -w $ARG1$ -c $ARG2$ -p $ARG3$
      command_name                   check_local_disk
    }

    define command {
      command_line                   $USER1$/check_load -w $ARG1$ -c $ARG2$
      command_name                   check_local_load
    }

    define command {
      command_line                   $USER1$/check_mrtgtraf -F $ARG1$ -a $ARG2$ -w $ARG3$ -c $ARG4$ -e $ARG5$
      command_name                   check_local_mrtgtraf
    }

    define command {
      command_line                   $USER1$/check_procs -w $ARG1$ -c $ARG2$ -s $ARG3$
      command_name                   check_local_procs
    }

    define command {
      command_line                   $USER1$/check_swap -w $ARG1$ -c $ARG2$
      command_name                   check_local_swap
    }

    define command {
      command_line                   $USER1$/check_users -w $ARG1$ -c $ARG2$
      command_name                   check_local_users
    }

    define command {
      command_line                   $USER1$/check_nt -H $HOSTADDRESS$ -p 12489 -v $ARG1$ $ARG2$
      command_name                   check_nt
    }

    define command {
      command_line                   $USER1$/check_ping -H $HOSTADDRESS$ -w $ARG1$ -c $ARG2$ -p 5
      command_name                   check_ping
    }

    define command {
      command_line                   $USER1$/check_pop -H $HOSTADDRESS$ $ARG1$
      command_name                   check_pop
    }

    define command {
      command_line                   $USER1$/check_smtp -H $HOSTADDRESS$ $ARG1$
      command_name                   check_smtp
    }

    define command {
      command_line                   $USER1$/check_snmp -H $HOSTADDRESS$ $ARG1$
      command_name                   check_snmp
    }

    define command {
      command_line                   $USER1$/check_ssh $ARG1$ $HOSTADDRESS$
      command_name                   check_ssh
    }

    define command {
      command_line                   $USER1$/check_tcp -H $HOSTADDRESS$ -p $ARG1$ $ARG2$
      command_name                   check_tcp
    }

    define command {
      command_line                   $USER1$/check_udp -H $HOSTADDRESS$ -p $ARG1$ $ARG2$
      command_name                   check_udp
    }

    define contact {
      name                           generic-contact
      host_notification_commands     notify-host-by-email
      host_notification_options      d,u,r,f,s
      host_notification_period       24x7
      register                       0
      service_notification_commands  notify-service-by-email
      service_notification_options   w,u,c,r,f,s
      service_notification_period    24x7
    }

    define host {
      name                           generic-host
      event_handler_enabled          1
      failure_prediction_enabled     1
      flap_detection_enabled         1
      notification_period            24x7
      notifications_enabled          1
      process_perf_data              1
      register                       0
      retain_nonstatus_information   1
      retain_status_information      1
      max_check_attempts             3
    }

    define host {
      name                           generic-printer
      use                            generic-host
      check_command                  check-host-alive
      check_interval                 5
      check_period                   24x7
      contact_groups                 admins
      max_check_attempts             10
      notification_interval          30
      notification_options           d,r
      notification_period            workhours
      register                       0
      retry_interval                 1
      statusmap_image                printer.png
    }

    define host {
      name                           generic-router
      use                            generic-switch
      register                       0
      statusmap_image                router.png
    }

    define service {
      name                           generic-service
      action_url                     /pnp4nagios/graph?host=$HOSTNAME$&srv=$SERVICEDESC$
      active_checks_enabled          1
      check_freshness                0
      check_period                   24x7
      event_handler_enabled          1
      failure_prediction_enabled     1
      flap_detection_enabled         1
      icon_image                     unknown.gif
      is_volatile                    0
      max_check_attempts             3
      normal_check_interval          10
      notes_url                      /adagios/objectbrowser/edit_object/object_type=service/shortname=$HOSTNAME$/$SERVICEDESC$
      notification_interval          60
      notification_options           w,u,c,r
      notification_period            24x7
      notifications_enabled          1
      obsess_over_service            1
      parallelize_check              1
      passive_checks_enabled         1
      process_perf_data              1
      register                       0
      retain_nonstatus_information   1
      retain_status_information      1
      retry_check_interval           2
    }

    define host {
      name                           generic-switch
      use                            generic-host
      check_command                  check-host-alive
      check_interval                 5
      check_period                   24x7
      contact_groups                 admins
      max_check_attempts             10
      notification_interval          30
      notification_options           d,r
      notification_period            24x7
      register                       0
      retry_interval                 1
      statusmap_image                switch.png
    }

    define host {
      name                           linux-server
      use                            generic-host
      check_command                  check-host-alive
      check_interval                 5
      check_period                   24x7
      contact_groups                 admins
      max_check_attempts             10
      notification_interval          120
      notification_options           d,u,r
      notification_period            workhours
      register                       0
      retry_interval                 1
    }

    define service {
      name                           local-service
      use                            generic-service
      max_check_attempts             4
      normal_check_interval          5
      register                       0
      retry_check_interval           1
    }

    define contact {
      use                            generic-contact
      alias                          Nagios Admin
      contact_name                   nagiosadmin
      email                          nagios@localhost
    }

    define timeperiod {
      alias                          No Time Is A Good Time
      timeperiod_name                none
    }

    define command {
      command_line                   /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$
      command_name                   notify-host-by-email
    }

    define command {
      command_line                   /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\n\nService: $SERVICEDESC$\nHost: $HOSTALIAS$\nAddress: $HOSTADDRESS$\nState: $SERVICESTATE$\n\nDate/Time: $LONGDATETIME$\n\nAdditional Info:\n\n$SERVICEOUTPUT$\n" | /bin/mail -s "** $NOTIFICATIONTYPE$ Service Alert: $HOSTALIAS$/$SERVICEDESC$ is $SERVICESTATE$ **" $CONTACTEMAIL$
      command_name                   notify-service-by-email
    }

    define command {
      command_line                   /usr/bin/printf "%b" "$LASTHOSTCHECK$\t$HOSTNAME$\t$HOSTSTATE$\t$HOSTATTEMPT$\t$HOSTSTATETYPE$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$\n" >> /var/log/nagios/host-perfdata.out
      command_name                   process-host-perfdata
    }

    define command {
      command_line                   /usr/bin/printf "%b" "$LASTSERVICECHECK$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICESTATE$\t$SERVICEATTEMPT$\t$SERVICESTATETYPE$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t$SERVICEPERFDATA$\n" >> /var/log/nagios/service-perfdata.out
      command_name                   process-service-perfdata
    }

    define timeperiod {
      alias                          U.S. Holidays
      december 25             00:00-00:00     ; Christmas
      january 1               00:00-00:00     ; New Years
      july 4                  00:00-00:00     ; Independence Day
      monday -1 may           00:00-00:00     ; Memorial Day (last Monday in May)
      monday 1 september      00:00-00:00     ; Labor Day (first Monday in September)
      name			us-holidays
      thursday 4 november     00:00-00:00     ; Thanksgiving (4th Thursday in November)
      timeperiod_name                us-holidays
    }

    define host {
      name                           windows-server
      use                            generic-host
      check_command                  check-host-alive
      check_interval                 5
      check_period                   24x7
      contact_groups                 admins
      hostgroups
      max_check_attempts             10
      notification_interval          30
      notification_options           d,r
      notification_period            24x7
      register                       0
      retry_interval                 1
    }

    define hostgroup {
      alias                          Windows Servers
      hostgroup_name                 windows-servers
    }

    define timeperiod {
      alias                 Normal Work Hours
      friday                09:00-17:00
      monday                09:00-17:00
      thursday              09:00-17:00
      timeperiod_name       workhours
      tuesday               09:00-17:00
      wednesday             09:00-17:00
    }

    define command {
        command_name    check_dummy
        command_line    $USER1$/check_dummy!$ARG1$!$ARG2$
    }


    define host {
        host_name           ok_host
        use                 generic-host
        address	            ok_host
        max_check_attempts	1
        check_command       check_dummy!0!Everything seems to be okay
    }


    define service {
        host_name           ok_host
        use                 generic-service
        service_description ok service 1
        check_command       check_dummy!0!Everything seems to be okay
    }

    """
