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

# <<<mssql_tablespaces>>>
# MSSQL_SQLEXPRESS master 5.25 MB 1.59 MB 2464 KB 1096 KB 1024 KB 344 KB
# MSSQL_SQLEXPRESS model 3.00 MB 1.13 MB 1152 KB 472 KB 632 KB 48 KB
# MSSQL_SQLEXPRESS msdb 18.13 MB 4.05 MB 10960 KB 8336 KB 2080 KB 544 KB
# MSSQL_SQLEXPRESS tempdb 2.75 MB 1.08 MB 1200 KB 480 KB 672 KB 48 KB
# MSSQL_SQLEXPRESS test123 4.00 MB 1.78 MB 1248 KB 528 KB 648 KB 72 KB

#  0: process instance
#  1: tablespace name
#  2: db size (Size of the current database in megabytes.
#     database_size includes both data and log files.)
#  3: uom
#  4: unallocated space (Space in the database that has not been reserved for database objects.)
#  5: uom
#  6: reserved space (Total amount of space allocated by objects in the database.)
#  7: uom
#  8: Total amount of space used by data.
#  9: uom
# 10: Total amount of space used by indexes.
# 11: uom
# 12: Total amount of space reserved for objects in the database, but not yet used.
# 13: uom

factory_settings["mssql_tablespace_default_levels"] = {}


def parse_mssql_tablespaces(info):
    def to_bytes(value, uom):
        exponent = {"KB": 1, "MB": 2, "GB": 3, "TB": 4}.get(uom, 0)
        try:
            return float(value) * (1024**exponent)
        except ValueError:
            return None

    parsed = {}
    for line in info:
        if len(line) < 14:
            continue

        pairs = list(zip(line[:14:2], line[1:14:2]))
        item = "%s %s" % pairs[0]
        keys = ("size", "unallocated", "reserved", "data", "indexes", "unused")
        values = (to_bytes(*p) for p in pairs[1:])
        data = dict(zip(keys, values))

        if len(line) > 14 and line[14].startswith("ERROR:"):
            data["error"] = " ".join(line[15:])

        parsed.setdefault(item, data)

    return parsed


def check_mssql_tablespaces(item, params, parsed):
    tablespace = parsed.get(item)
    if tablespace is None:
        # Assume general connection problem to the database, which is reported
        # by the "X Instance" service and skip this check.
        raise MKCounterWrapped("Tablespace not found")

    size = tablespace["size"]
    if "error" in tablespace:
        yield 2, tablespace["error"]

    if size is not None:
        levels = params.get("size", (None, None))
        yield check_levels(
            size, "size", levels, human_readable_func=get_bytes_human_readable, infoname="Size"
        )

    for key, title, is_upper in [
        ("unallocated", "Unallocated space", False),
        ("reserved", "Reserved space", True),
        ("data", "Data", True),
        ("indexes", "Indexes", True),
        ("unused", "Unused", True),
    ]:
        value_bytes = tablespace[key]
        if value_bytes is None:
            continue

        infotext = "%s: %s" % (title, get_bytes_human_readable(value_bytes))
        try:
            value_perc = 100.0 * value_bytes / size
            infotext += ", %s" % get_percent_human_readable(value_perc)
        except (TypeError, ZeroDivisionError):
            value_perc = None

        warn, crit = params.get(key, (None, None))
        if isinstance(crit, float) and value_perc is not None:
            value = value_perc
            readable_f = get_percent_human_readable
        elif isinstance(warn, int):
            value = value_bytes
            readable_f = get_bytes_human_readable
        else:
            yield 0, infotext, [(key, value_bytes)]
            continue

        state = 0
        if is_upper and value >= crit or not is_upper and value <= crit:
            state = 2
        elif is_upper and value >= warn or not is_upper and value <= warn:
            state = 1
        if state:
            infotext += " (warn/crit %s %s/%s)" % (
                ("below", "at")[is_upper],
                readable_f(warn),
                readable_f(crit),
            )
        yield state, infotext, [(key, value_bytes)]


check_info["mssql_tablespaces"] = {
    "parse_function": parse_mssql_tablespaces,
    "inventory_function": discover(),
    "check_function": check_mssql_tablespaces,
    "service_description": "MSSQL %s Sizes",
    "group": "mssql_tablespaces",
    "has_perfdata": True,
    "default_levels_variable": "mssql_tablespace_default_levels",
}
