#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 tribe29 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.

# In cooperation with Thorsten Bruhns from OPITZ Consulting

# <<<oracle_dataguard_stats:sep(124)>>>
# TESTDB|TESTDBU2|PHYSICAL STANDBY|apply finish time|+00 00:00:00.000|NOT ALLOWED|ENABLED|MAXIMUM PERFORMANCE|DISABLED||||APPLYING_LOG
# TESTDB|TESTDBU2|PHYSICAL STANDBY|apply lag|+00 00:00:00|NOT ALLOWED|ENABLED|MAXIMUM PERFORMANCE|DISABLED||||APPLYING_LOG
#
# TUX12C|TUXSTDB|PHYSICAL STANDBY|transport lag|+00 00:00:00
# TUX12C|TUXSTDB|PHYSICAL STANDBY|apply lag|+00 00:28:57
# TUX12C|TUXSTDB|PHYSICAL STANDBY|apply finish time|+00 00:00:17.180
# TUX12C|TUXSTDB|PHYSICAL STANDBY|estimated startup time|20

factory_settings["oracle_dataguard_stats"] = {
    "apply_lag": (3600, 14400),
    "active_dataguard_option": 1,
    "primary_broker_state": False,
    "mrp_option": (0, 2),
}


def inventory_oracle_dataguard_stats(parsed):
    for instance in parsed:
        yield instance, {}


def check_oracle_dataguard_stats(item, params, parsed):
    def get_seconds(timestamp):
        if not timestamp:
            return None
        elif str(timestamp)[0:1] == "+":
            days = int(timestamp[1:3])
            h = int(timestamp[4:6])
            min_ = int(timestamp[7:9])
            sec = int(timestamp[10:12])

            seconds = int(sec + min_ * 60 + h * 3600 + days * 24 * 3600)
            return seconds
        return None

    state = 0

    try:
        dgdata = parsed[item]

    except KeyError:

        # In case of missing information we assume that the login into
        # the database has failed and we simply skip this check. It won't
        # switch to UNKNOWN, but will get stale.
        raise MKCounterWrapped("Dataguard disabled or Instance not running")

    yield 0, "Database Role %s" % (dgdata["database_role"].lower())

    if "protection_mode" in dgdata:
        yield 0, "Protection Mode %s" % (dgdata["protection_mode"].lower())

    if "broker_state" in dgdata:
        yield 0, "Broker %s" % (dgdata["broker_state"].lower())

        # Observer is only usable with enabled Fast Start Failover!
        if "fs_failover_status" in dgdata and dgdata["fs_failover_status"] != "DISABLED":

            if dgdata["fs_failover_observer_present"] != "YES":
                yield 2, "Observer not connected"
            else:
                yield 0, "Observer connected %s from host %s" % (
                    dgdata["fs_failover_observer_present"].lower(),
                    dgdata["fs_failover_observer_host"],
                )

                if (
                    dgdata["protection_mode"] == "MAXIMUM PERFORMANCE"
                    and dgdata["fs_failover_status"] == "TARGET UNDER LAG LIMIT"
                ) or (
                    dgdata["protection_mode"] == "MAXIMUM AVAILABILITY"
                    and dgdata["fs_failover_status"] == "SYNCHRONIZED"
                ):
                    state = 0
                else:
                    state = 1
                yield state, "Fast Start Failover %s" % (dgdata["fs_failover_status"].lower())

    # switchover_status is important for non broker environemnts as well.
    if "switchover_status" in dgdata:

        if dgdata["database_role"] == "PRIMARY":
            if dgdata["switchover_status"] in (
                "TO STANDBY",
                "SESSIONS ACTIVE",
                "RESOLVABLE GAP",
                "LOG SWITCH GAP",
            ):
                yield 0, "Switchover to standby possible"
            else:
                primary_broker_state = params.get("primary_broker_state")
                if primary_broker_state or dgdata["broker_state"].lower() == "enabled":
                    # We need primary_broker_state False for Data-Guards without Broker
                    yield 2, "Switchover to standby not possible! reason: %s" % dgdata[
                        "switchover_status"
                    ].lower()
                else:
                    yield 0, "Switchoverstate ignored "

        elif dgdata["database_role"] == "PHYSICAL STANDBY":

            # don't show the ok state, due to distracting 'NOT ALLOWED' state!
            if dgdata["switchover_status"] in ("SYNCHRONIZED", "NOT ALLOWED", "SESSIONS ACTIVE"):
                yield 0, "Switchover to primary possible"
            else:
                yield 2, "Switchover to primary not possible! reason: %s" % dgdata[
                    "switchover_status"
                ]

    if dgdata["database_role"] == "PHYSICAL STANDBY":
        mrp_status = dgdata.get("mrp_status")

        mrp_started_state, mrp_stopped_state = params.get("mrp_option", (0, 2))
        if mrp_status:
            yield mrp_started_state, "Managed Recovery Process state %s" % mrp_status.lower()

            if dgdata.get("open_mode", "") == "READ ONLY WITH APPLY":
                yield params.get("active_dataguard_option"), "Active Data-Guard found"
        elif mrp_status is not None:
            yield mrp_stopped_state, "Managed Recovery Process not started"

        for dgstat_param in sorted(dgdata["dgstat"]):

            if dgstat_param == "apply lag" and not dgdata["dgstat"][dgstat_param]:
                yield 1, "No value for apply lag"

            if dgstat_param in ("apply lag", "transport lag") and dgdata["dgstat"][dgstat_param]:
                seconds = get_seconds(dgdata["dgstat"][dgstat_param])
                state = 0
                if dgdata["dgstat"][dgstat_param]:

                    paramtext = ""
                    warn = None
                    crit = None

                    if params.get(dgstat_param.replace(" ", "_")):

                        warn, crit = params.get(dgstat_param.replace(" ", "_"))

                        if seconds is None:
                            # => This is only for safety. This point is not reached with soconds=None from parsed function!
                            state = 3
                        elif crit <= seconds:
                            paramtext = "(%s/%s)" % (
                                get_age_human_readable(warn),
                                get_age_human_readable(crit),
                            )
                            state = 2
                        elif warn <= seconds:
                            paramtext = "(%s/%s)" % (
                                get_age_human_readable(warn),
                                get_age_human_readable(crit),
                            )
                            state = 1

                        # apply_lag_min is a MINIMUM value!
                        if dgstat_param == "apply lag" and params.get("apply_lag_min"):
                            warn, crit = params.get("apply_lag_min")
                            if seconds is None:
                                # => This is only for safety. This point is not reached with soconds=None from parsed function!
                                state = 3
                            elif crit >= seconds:
                                paramtext += " apply lag minimum (%s/%s)" % (
                                    get_age_human_readable(warn),
                                    get_age_human_readable(crit),
                                )
                                state = max(state, 2)
                            elif warn >= seconds:
                                paramtext += " apply lag minimum (%s/%s)" % (
                                    get_age_human_readable(warn),
                                    get_age_human_readable(crit),
                                )
                                state = max(state, 1)

                perfdata = [
                    (dgstat_param.replace(" ", "_"), seconds, warn, crit),
                ]

                if state == 0:
                    yield 0, "%s %s" % (dgstat_param, get_age_human_readable(seconds)), perfdata
                else:
                    if seconds is None:
                        # seconds is None => state != 0 from upper checks
                        yield state, "%s unknown time" % (dgstat_param), perfdata
                    else:
                        yield state, "%s %s %s" % (
                            dgstat_param,
                            get_age_human_readable(seconds),
                            paramtext,
                        ), perfdata

            # show only 'apply finish time'

            elif dgstat_param == "apply finish time":
                if dgdata["dgstat"][dgstat_param]:
                    seconds = get_seconds(dgdata["dgstat"][dgstat_param])
                    perfdata = [
                        (dgstat_param.replace(" ", "_"), seconds),
                    ]
                    if seconds is None:
                        # apply finish time is only for informational purposes
                        # => we ignore possible unknown time for apply finish times
                        yield 0, "%s unknown time" % (dgstat_param), perfdata
                    else:
                        yield 0, "%s %s" % (dgstat_param, get_age_human_readable(seconds)), perfdata

        if (
            dgdata["database_role"] == "PHYSICAL STANDBY"
            and "broker_state" not in dgdata
            and "apply lag" in dgdata["dgstat"]
            and dgdata["dgstat"]["apply lag"] == ""
        ):
            # old sql cannot detect a started standby database without running media recovery
            # => add an information for old plugin with possible wrong result
            yield 0, "old plugin data found, recovery active?"


check_info["oracle_dataguard_stats"] = {
    # section is already migrated!
    "check_function": check_oracle_dataguard_stats,
    "inventory_function": inventory_oracle_dataguard_stats,
    "service_description": "ORA %s Dataguard-Stats",
    "has_perfdata": True,
    "default_levels_variable": "oracle_dataguard_stats",
    "group": "oracle_dataguard_stats",
}
