#!/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

# <<<oracle_instance:sep(124)>>>
# TUX2|12.1.0.1.0|OPEN|ALLOWED|STARTED|6735|1297771692|ARCHIVELOG|PRIMARY|NO|TUX2
# TUX5|12.1.0.1.1|MOUNTED|ALLOWED|STARTED|82883|1297771692|NOARCHIVELOG|PRIMARY|NO|0|TUX5

# <<<oracle_instance:sep(124)>>>$
# +ASM|FAILURE|ORA-99999 tnsping failed for +ASM $
# ERROR:$
# ORA-28002: the password will expire within 1 days$

import datetime

from cmk.base.plugins.agent_based.oracle_instance import GeneralError, Instance, InvalidData

factory_settings["oracle_instance_defaults"] = {
    "logins": 2,
    "noforcelogging": 1,
    "noarchivelog": 1,
    "primarynotopen": 2,
}


def _transform_oracle_instance_params(p):
    if "ignore_noarchivelog" in p:
        if p["ignore_noarchivelog"]:
            p["noarchivelog"] = 0
        del p["ignore_noarchivelog"]
    return p


def check_oracle_instance(item, params, section):
    if not (item_data := section.get(item)):
        return 2, "Database or necessary processes not running or login failed"

    params = _transform_oracle_instance_params(params)

    def state_marker(state, infotext, param, column, data):
        value = params.get(param)
        if value is not None and column.lower() == data.lower():
            state = max(state, value)
            if value == 1:
                infotext += "(!)"
            elif value == 2:
                infotext += "(!!)"
        return state, infotext

    if isinstance(
        item_data,
        GeneralError,
    ):
        return 2, item_data.err

    if isinstance(
        item_data,
        InvalidData,
    ):
        return 2, "Database not running, login failed or unvalid data from agent"

    state = 0

    # Handle old oracle agent plugin output
    if item_data.old_agent:
        infotext = "Status %s, Version %s, Logins %s" % (
            item_data.openmode,
            item_data.version,
            item_data.logins.lower(),
        )
        state, infotext = state_marker(state, infotext, "logins", item_data.logins, "RESTRICTED")
        return state, infotext

    if item_data.pdb:
        infotext = "PDB Name %s.%s, Status %s" % (
            item_data.name,
            item_data.pname,
            item_data.popenmode,
        )
    else:
        if item_data.pluggable.lower() == "true":
            infotext = "CDB Name %s, Status %s" % (item_data.name, item_data.openmode)
        else:
            infotext = "Database Name %s, Status %s" % (item_data.name, item_data.openmode)

    # Check state for PRIMARY Database. Normaly there are always OPEN
    if item_data.database_role == "PRIMARY" and item_data.openmode not in (
        "OPEN",
        "READ ONLY",
        "READ WRITE",
    ):
        state = params.get("primarynotopen")
        if state == 1:
            infotext += "(!)"
        elif state == 2:
            infotext += "(!!)"
        elif state == 0:
            infotext += " (allowed by rule)"

    if not item_data.pdb:
        infotext += ", Role %s, Version %s" % (item_data.database_role, item_data.version)

    # ASM has no login and archivelog check
    if item_data.database_role != "ASM":

        # logins are only possible when the database is open
        if item_data.openmode == "OPEN":
            infotext += ", Logins %s" % (item_data.logins.lower())
            state, infotext = state_marker(
                state, infotext, "logins", item_data.logins, "RESTRICTED"
            )

        # the new internal database _MGMTDB from 12.1.0.2 is always in NOARCHIVELOG mode
        if item_data.name != "_MGMTDB" and item_data.sid != "-MGMTDB" and not item_data.pdb:
            infotext += ", Log Mode %s" % (item_data.log_mode.lower())
            state, infotext = state_marker(
                state, infotext, "archivelog", item_data.log_mode, "ARCHIVELOG"
            )
            state, infotext = state_marker(
                state, infotext, "noarchivelog", item_data.log_mode, "NOARCHIVELOG"
            )

            # archivelog is only valid in non pdb
            # force logging is only usable when archivelog is enabled
            if item_data.log_mode == "ARCHIVELOG":
                if item_data.archiver != "STARTED":
                    infotext += ". Archiver %s(!!)" % (item_data.archiver.lower())
                    state = 2

                infotext += ", Force Logging %s" % (item_data.force_logging.lower())
                state, infotext = state_marker(
                    state, infotext, "forcelogging", item_data.force_logging, "YES"
                )
                state, infotext = state_marker(
                    state, infotext, "noforcelogging", item_data.force_logging, "NO"
                )

    perfdata = []

    if item_data.pdb:
        infotext += ", PDB Size %s" % get_bytes_human_readable(int(item_data.ptotal_size))
        perfdata.append(("fs_size", int(item_data.ptotal_size)))

    return state, infotext, perfdata


check_info["oracle_instance"] = {
    # section is already migrated!
    "check_function": check_oracle_instance,
    "inventory_function": discover(),
    "service_description": "ORA %s Instance",
    "default_levels_variable": "oracle_instance_defaults",
    "group": "oracle_instance",
    "has_perfdata": True,
}


def discover_oracle_instance_uptime(parsed):
    yield from (
        (
            item,
            {},
        )
        for item, data in parsed.items()
        if isinstance(
            data,
            Instance,
        )
    )


def check_oracle_instance_uptime(item, params, parsed):
    item_data = parsed.get(item)
    if item_data is None or not isinstance(
        item_data,
        Instance,
    ):
        # Error is already shown in main check
        raise MKCounterWrapped("Login into database failed")

    up_seconds = max(0, int(item_data.up_seconds))

    params = params.get("max", (None, None)) + params.get("min", (None, None))
    return check_levels(
        up_seconds,
        "uptime",
        params,
        human_readable_func=lambda x: datetime.timedelta(seconds=int(x)),
        infoname="Up since %s, uptime"
        % time.strftime("%F %T", time.localtime(time.time() - up_seconds)),
    )


check_info["oracle_instance.uptime"] = {
    "check_function": check_oracle_instance_uptime,
    "inventory_function": discover_oracle_instance_uptime,
    "service_description": "ORA %s Uptime",
    "group": "uptime_multiitem",
    "has_perfdata": True,
}
