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

factory_settings["cisco_ip_sla_default_levels"] = {
    "state": "active",
    "connection_lost_occured": "no",
    "timeout_occured": "no",
    "completion_time_over_treshold_occured": "no",
    "latest_rtt_completion_time": (250, 500),
    "latest_rtt_state": "ok",
    "packets_lost_src->dest": (100, 1000),
    "packets_lost_dest->src": (100, 1000),
}


def parse_cisco_ip_sla(info):
    precisions = {line[0]: "ms" if line[-1] == "1" else "us" for line in info[0]}

    rtt_types = {
        "1": "echo",
        "2": "path echo",
        "3": "file IO",
        "4": "script",
        "5": "UDP echo",
        "6": "TCP connect",
        "7": "HTTP",
        "8": "DNS",
        "9": "jitter",
        "10": "DLSw",
        "11": "DHCP",
        "12": "FTP",
        "13": "VoIP",
        "14": "RTP",
        "15": "LSP group",
        "16": "ICMP jitter",
        "17": "LSP ping",
        "18": "LSP trace",
        "19": "ethernet ping",
        "20": "ethernet jitter",
        "21": "LSP ping pseudowire",
    }

    states = {
        "1": "reset",
        "2": "orderly stop",
        "3": "immediate stop",
        "4": "pending",
        "5": "inactive",
        "6": "active",
        "7": "restart",
    }

    rtt_states = {
        "0": "other",
        "1": "ok",
        "2": "disconnected",
        "3": "over threshold",
        "4": "timeout",
        "5": "busy",
        "6": "not connected",
        "7": "dropped",
        "8": "sequence error",
        "9": "verify error",
        "10": "application specific error",
    }

    def to_ip_address(int_list):
        if len(int_list) == 4:
            return "%d.%d.%d.%d" % tuple(int_list)
        elif len(int_list) == 6:
            return "%d:%d:%d:%d:%d:%d" % tuple(int_list)
        return ""

    # contains description, parse function, unit and type
    contents = [
        (  # rttMonEchoAdminEntry
            ("Target address", to_ip_address, "", None),
            ("Source address", to_ip_address, "", None),
            # rttMonEchoAdminPrecision is deliberatly dropped by zip below
        ),
        (  # rttMonCtrlAdminEntry
            ("Owner", None, "", None),
            ("Tag", None, "", None),
            ("RTT type", lambda x: rtt_types.get(x, "unknown"), "", "option"),
            ("Threshold", int, "ms", "option"),
        ),
        (  # rttMonCtrlOperEntry
            ("State", lambda x: states.get(x, "unknown"), "", "option"),
            ("Text", None, "", None),
            ("Connection lost occured", lambda x: "yes" if x == "1" else "no", "", "option"),
            ("Timeout occured", lambda x: "yes" if x == "1" else "no", "", "option"),
            (
                "Completion time over treshold occured",
                lambda x: "yes" if x == "1" else "no",
                "",
                "option",
            ),
        ),
        (  # rttMonLatestRttOperEntry
            ("Latest RTT completion time", int, "ms/us", "level"),
            ("Latest RTT state", lambda x: rtt_states.get(x, "unknown"), "", "option"),
        ),
        (  #  rttMonJitterStatsEntry
            ("Packets lost src->dest", int, "packets", "level"),
            ("Packets lost dest->src", int, "packets", "level"),
        ),
    ]

    parsed = {}
    for content, entries in zip(contents, info):
        if not entries:
            continue

        for entry in entries:
            index, values = entry[0], entry[1:]
            data = parsed.setdefault(index, [])
            for (description, parser, unit, type_), value in zip(content, values):
                if parser:
                    value = parser(value)
                if unit == "ms/us":
                    unit = precisions[index]
                data.append((description, value, unit, type_))

    return parsed


def inventory_cisco_ip_sla(parsed):
    for index in parsed:
        yield index, {}


@get_parsed_item_data
def check_cisco_ip_sla(_item, params, data):
    for description, value, unit, type_ in data:
        if not value:
            continue

        state = 0
        if unit:
            infotext = "%s: %s %s" % (description, value, unit)
        else:
            infotext = "%s: %s" % (description, value)
        perfdata = []

        param = params.get(description.lower().replace(" ", "_"))

        if type_ == "option":
            if param and param != value:
                state = 1
                infotext += " (expected %s)" % param
        elif type_ == "level":
            warn, crit = param  # a default level hat to exist
            if value >= crit:
                state = 2
            elif value >= warn:
                state = 1

            if state:
                infotext += " (warn/crit at %s/%s)" % (warn, crit)

            if unit == "ms/us":
                factor = 1e3 if unit == "ms" else 1e6
                perfdata = [
                    ("rtt", value / factor, warn / factor, crit / factor)
                ]  # fixed: true-division
            elif unit == "packets":
                perfdata = [
                    ("lost", value, warn, crit)
                ]

        yield state, infotext, perfdata


check_info["cisco_ip_sla"] = {
    "parse_function": parse_cisco_ip_sla,
    "inventory_function": inventory_cisco_ip_sla,
    "check_function": check_cisco_ip_sla,
    "service_description": "Cisco IP SLA %s",
    "group": "cisco_ip_sla",
    "default_levels_variable": "cisco_ip_sla_default_levels",
    "has_perfdata": True,
    "snmp_scan_function": lambda oid: "cisco" in oid(".1.3.6.1.2.1.1.1.0").lower()
    and "ios" in oid(".1.3.6.1.2.1.1.1.0").lower()
    and oid(".1.3.6.1.4.1.9.9.42.1.2.2.1.37.*"),
    "snmp_info": [
        (
            ".1.3.6.1.4.1.9.9.42.1.2.2.1",
            [
                OID_END,
                BINARY(2),  # rttMonEchoAdminTargetAddress
                BINARY(6),  # rttMonEchoAdminSourceAddress
                # only needed to determine the unit (ms/us)
                37,  # rttMonEchoAdminPrecision
            ],
        ),
        (
            ".1.3.6.1.4.1.9.9.42.1.2.1.1",
            [
                OID_END,
                2,  # rttMonCtrlAdminOwner
                3,  # rttMonCtrlAdminTag
                4,  # rttMonCtrlAdminRttType
                5,  # rttMonCtrlAdminThreshold
            ],
        ),
        (
            ".1.3.6.1.4.1.9.9.42.1.2.9.1",
            [
                OID_END,
                10,  # rttMonCtrlOperState
                2,  # rttMonCtrlOperDiagText
                5,  # rttMonCtrlOperConnectionLostOccurred
                6,  # rttMonCtrlOperTimeoutOccurred
                7,  # rttMonCtrlOperOverThresholdOccurred
            ],
        ),
        (
            ".1.3.6.1.4.1.9.9.42.1.2.10.1",
            [
                OID_END,
                1,  # rttMonLatestRttOperCompletionTime
                2,  # rttMonLatestRttOperSense
            ],
        ),
        (
            ".1.3.6.1.4.1.9.9.42.1.5.2.1",
            [
                OID_END,
                26,  # rttMonLatestJitterOperPacketLossSD
                27,  # rttMonLatestJitterOperPacketLossDS
            ]
        )
    ],
}
