#!/usr/bin/env python3
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# Future convention within all Checkmk modules for variable names:
#
# - host_name     - Monitoring name of a host (string)
# - node_name     - Name of cluster member (string)
# - cluster_name  - Name of a cluster (string)
# - realhost_name - Name of a *real* host, not a cluster (string)

import errno
import getopt
import logging
import os
import sys

# Needs to be placed before cmk modules, because they are not available
# when executed as non site user. Alas, this screws up our import ordering
# a bit, so we have to tell pylint about that.
# pylint: disable=wrong-import-position
if not os.environ.get("OMD_SITE"):
    sys.stderr.write("Check_MK can be used only as site user.\n")
    sys.exit(1)

import cmk.utils.caching
import cmk.utils.debug
import cmk.utils.log
import cmk.utils.paths
from cmk.utils.crash_reporting import ABCCrashReport, crash_report_registry, CrashReportStore
from cmk.utils.exceptions import MKBailOut, MKGeneralException, MKTerminate
from cmk.utils.log import console

import cmk.base.check_api as check_api
import cmk.base.config as config
import cmk.base.profiling as profiling
import cmk.base.utils
from cmk.base.modes import modes

cmk.utils.log.setup_console_logging()
logger = logging.getLogger("cmk.base")

cmk.base.utils.register_sigint_handler()

help_function = modes.get("help").handler_function


# We probably don't really need to register that here, do we?
@crash_report_registry.register
class CrashReport(ABCCrashReport):
    @classmethod
    def type(cls) -> str:
        return "base"

    @classmethod
    def from_exception(
        cls, details: object = None, type_specific_attributes: object = None
    ) -> ABCCrashReport:
        return super().from_exception(
            details={
                "argv": sys.argv,
                "env": dict(os.environ),
            }
        )


try:
    opts, args = getopt.getopt(sys.argv[1:], modes.short_getopt_specs(), modes.long_getopt_specs())
except getopt.GetoptError as err:
    console.error("ERROR: %s\n\n" % err)
    if help_function is None:
        raise TypeError()
    help_function()
    sys.exit(1)

# First load the general modifying options
modes.process_general_options(opts)

try:
    # Now find the requested mode and execute it
    mode_name, mode_args = None, None
    for o, a in opts:
        if modes.exists(o):
            mode_name, mode_args = o, a
            break

    if not opts and not args:
        if help_function is None:
            raise TypeError()
        help_function()
        sys.exit(0)

    # At least in case the config is needed, the checks are needed too, because
    # the configuration may refer to check config variable names.
    if mode_name not in modes.non_checks_options():
        errors = config.load_all_plugins(
            check_api.get_check_api_context,
            local_checks_dir=cmk.utils.paths.local_checks_dir,
            checks_dir=cmk.utils.paths.checks_dir,
        )
        if sys.stderr.isatty():
            for error_msg in errors:
                console.error(error_msg)

    # Read the configuration files (main.mk, autochecks, etc.), but not for
    # certain operation modes that does not need them and should not be harmed
    # by a broken configuration
    if mode_name not in modes.non_config_options():
        config.load()

    done, exit_status = False, 0
    if mode_name is not None and mode_args is not None:
        exit_status = modes.call(mode_name, mode_args, opts, args)
        done = True

    # When no mode was found, Checkmk is running the "check" mode
    if not done:
        if (args and len(args) <= 2) or "--keepalive" in [o[0] for o in opts]:
            exit_status = modes.call("--check", None, opts, args)
        else:
            help_function = modes.get("help").handler_function
            if help_function is None:
                raise TypeError()
            help_function()
            exit_status = 0

    sys.exit(exit_status)

except MKTerminate:
    sys.stderr.write("<Interrupted>\n")
    sys.exit(1)

except (MKGeneralException, MKBailOut) as e:
    sys.stderr.write("%s\n" % e)
    if cmk.utils.debug.enabled():
        raise
    sys.exit(3)

except OSError as e:
    if e.errno == errno.EPIPE:
        # this is not an error, caller closes socket(s) and will kill cmk too
        sys.exit(4)
    crash = CrashReport.from_exception()
    CrashReportStore().save(crash)
    raise

except Exception:
    crash = CrashReport.from_exception()
    CrashReportStore().save(crash)
    raise

finally:
    profiling.output_profile()
