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


# mypy: disable-error-code="var-annotated,arg-type"

from cmk.base.check_api import check_levels, LegacyCheckDefinition
from cmk.base.config import check_info

from cmk.agent_based.v2 import render


def parse_emcvnx_storage_pools(string_table):
    parsed = {}
    section = None
    pool_name = None
    tier_name = None
    for line in string_table:
        line = [x.strip() for x in line]

        if line[0].startswith("[[[") and line[0].endswith("]]]"):
            section = line[0][3:-3]

        elif (section == "storage_pools" and line[0] == "Pool Name") or (
            section == "auto_tiering" and line[0] == "Storage Pool Name"
        ):
            pool_name = line[1]
            parsed.setdefault(pool_name, {})

        elif section == "auto_tiering" and line[0].startswith("Auto-tiering is not supported"):
            pool_name = None

        elif pool_name is not None and len(line) == 2:
            if line[0] == "Tier Name":
                tier_name = line[1]
                parsed[pool_name].setdefault("tier_names", [])
                parsed[pool_name]["tier_names"].append(tier_name)

            elif line[0] == "Disks (Type)":
                tier_name = None

            elif tier_name is not None:
                parsed[pool_name].setdefault(f"{tier_name}_{line[0]}", line[1])

            else:
                parsed[pool_name].setdefault(line[0], line[1])

    return parsed


def inventory_emcvnx_storage_pools(parsed):
    for pool_name in parsed:
        yield pool_name, {}


#   .--general-------------------------------------------------------------.
#   |                                                  _                   |
#   |                   __ _  ___ _ __   ___ _ __ __ _| |                  |
#   |                  / _` |/ _ \ '_ \ / _ \ '__/ _` | |                  |
#   |                 | (_| |  __/ | | |  __/ | | (_| | |                  |
#   |                  \__, |\___|_| |_|\___|_|  \__,_|_|                  |
#   |                  |___/                                               |
#   '----------------------------------------------------------------------'

# Suggested by customer


def check_emcvnx_storage_pools(item, params, parsed):
    # Better readable names in web GUI of device:
    # "User Capacity (GBs)"             : "Physical Capacity: Total",
    # "Consumed Capacity (GBs)"         : "Physical Capacity: Total Allocation",
    # "Available Capacity (GBs)"        : "Physical Capacity: Free",
    # "Percent Full"                    : "Physical Capacity: Percent Full",
    # "Percent Subscribed"              : "Virtual Capacity: Percent Subscribed",
    # "Oversubscribed by (GBs)"         : "Virtual Capacity: Oversubscribed by",
    # "Total Subscribed Capacity (GBs)" : "Virtual Capacity: Total Subscription",

    if item in parsed:
        data = parsed[item]
        state = data["State"]
        status = data["Status"]
        user_capacity = float(data["User Capacity (GBs)"]) * 1024**3
        consumed_capacity = float(data["Consumed Capacity (GBs)"]) * 1024**3
        avail_capacity = float(data["Available Capacity (GBs)"]) * 1024**3
        percent_full = float(data["Percent Full"])
        percent_subscribed = float(data["Percent Subscribed"])
        over_subscribed = float(data["Oversubscribed by (GBs)"]) * 1024**3
        total_subscribed_capacity = float(data["Total Subscribed Capacity (GBs)"]) * 1024**3

        yield 0, (
            "State: %s, Status: %s, [Phys. capacity] User capacity: %s, "
            + "Consumed capacity: %s, Available capacity: %s"
        ) % (
            state,
            status,
            render.bytes(user_capacity),
            render.bytes(consumed_capacity),
            render.bytes(avail_capacity),
        )

        state = 0
        infotext = "Percent full: %s" % render.percent(percent_full)
        if "percent_full" in params:
            perc_full_warn, perc_full_crit = params["percent_full"]
            if percent_full >= perc_full_crit:
                state = 2
            elif percent_full >= perc_full_warn:
                state = 1
            if state:
                infotext += " (warn/crit at {}/{})".format(
                    render.bytes(perc_full_warn),
                    render.bytes(perc_full_crit),
                )

        yield state, infotext
        yield 0, (
            "[Virt. capacity] Percent subscribed: %s, Oversubscribed by: %s, "
            + "Total subscribed capacity: %s"
        ) % (
            render.percent(percent_subscribed),
            render.bytes(over_subscribed),
            render.bytes(total_subscribed_capacity),
        ), [
            ("emcvnx_consumed_capacity", consumed_capacity),
            ("emcvnx_avail_capacity", avail_capacity),
            ("emcvnx_perc_full", percent_full),
            ("emcvnx_perc_subscribed", percent_subscribed),
            ("emcvnx_over_subscribed", over_subscribed),
            ("emcvnx_total_subscribed_capacity", total_subscribed_capacity),
        ]


check_info["emcvnx_storage_pools"] = LegacyCheckDefinition(
    parse_function=parse_emcvnx_storage_pools,
    service_name="Pool %s General",
    discovery_function=inventory_emcvnx_storage_pools,
    check_function=check_emcvnx_storage_pools,
    check_ruleset_name="emcvnx_storage_pools",
    check_default_parameters={"percent_full": (70.0, 90.0)},
)

# .
#   .--tiering-------------------------------------------------------------.
#   |                  _                  _                                |
#   |                 | |_ ___  __ _ _ __(_)_ __   __ _                    |
#   |                 | __/ _ \/ _` | '__| | '_ \ / _` |                   |
#   |                 | ||  __/ (_| | |  | | | | | (_| |                   |
#   |                  \__\___|\__,_|_|  |_|_| |_|\__, |                   |
#   |                                             |___/                    |
#   '----------------------------------------------------------------------'

# Suggested by customer


def parse_emcvnx_time_to_complete(time_to_complete):
    map_units = {
        "day": 24 * 60 * 60,
        "hour": 60 * 60,
        "minute": 60,
    }
    # 11 days, 17 hours, 24 minutes
    # 17 hours, 24 minutes
    # 1 hour, 43 minutes
    # ...
    try:
        seconds = 0
        for value, unit in [x.strip().split() for x in time_to_complete.split(",")]:
            if unit.endswith("s"):
                unit = unit[:-1]
            seconds += int(value) * map_units[unit]
        return seconds
    except (ValueError, KeyError, AttributeError):
        return None


def inventory_emcvnx_storage_pools_tiering(parsed):
    for pool_name in parsed:
        yield pool_name, {}


def check_emcvnx_storage_pools_tiering(item, params, parsed):
    if not (data := parsed.get(item)):
        return
    for key in ("FAST Cache", "Relocation Status", "Relocation Rate"):
        if key in data:
            yield 0, f"{key.capitalize()}: {data[key]}"

    for direction in ("Up", "Down", "Within Tiers"):
        value_raw = data.get("Data to Move %s (GBs)" % direction)
        if value_raw is not None:
            value = float(value_raw) * 1024**3
            short_dir = direction.split()[0].lower()
            yield check_levels(
                value,
                "emcvnx_move_%s" % short_dir,
                None,
                infoname="Move %s" % short_dir,
                human_readable_func=render.bytes,
            )

    move_completed_raw = data.get("Data Movement Completed (GBs)")
    if move_completed_raw is not None:
        move_completed = float(move_completed_raw) * 1024**3
        yield check_levels(
            move_completed,
            "emcvnx_move_completed",
            None,
            infoname="Movement completed",
            human_readable_func=render.bytes,
        )

    time_to_complete = data.get("Estimated Time to Complete")
    age = parse_emcvnx_time_to_complete(time_to_complete)
    if age is not None:
        yield 0, "Estimated time to complete: %s" % time_to_complete
        yield check_levels(
            age,
            "emcvnx_time_to_complete",
            params["time_to_complete"],
            infoname="Age",
            human_readable_func=render.timespan,
        )


check_info["emcvnx_storage_pools.tiering"] = LegacyCheckDefinition(
    service_name="Pool %s Tiering Status",
    sections=["emcvnx_storage_pools"],
    discovery_function=inventory_emcvnx_storage_pools_tiering,
    check_function=check_emcvnx_storage_pools_tiering,
    check_ruleset_name="emcvnx_storage_pools_tiering",
    check_default_parameters={
        "time_to_complete": (21 * 60 * 60 * 24, 28 * 60 * 60 * 24),
    },
)


def inventory_emcvnx_storage_pools_tieringtypes(parsed):
    for pool_name, data in parsed.items():
        for tier_name in data.get("tier_names", []):
            yield f"{pool_name} {tier_name}", {}


def _get_item_data_and_tier(item, parsed):
    for pool_name, data in parsed.items():
        for tier_name in data.get("tier_names", []):
            if item == f"{pool_name} {tier_name}":
                return data, tier_name
    return None, None


def check_emcvnx_storage_pools_tieringtypes(item, params, parsed):
    data, tier_name = _get_item_data_and_tier(item, parsed)
    if data is None:
        return

    user_capacity_raw = data.get("%s_User Capacity (GBs)" % tier_name)
    if user_capacity_raw is not None:
        user_capacity = float(user_capacity_raw) * 1024**3
        yield check_levels(
            user_capacity,
            None,
            None,
            infoname="User capacity",
            human_readable_func=render.bytes,
        )

    consumed_capacity_raw = data.get("%s_Consumed Capacity (GBs)" % tier_name)
    if consumed_capacity_raw is not None:
        consumed_capacity = float(consumed_capacity_raw) * 1024**3
        yield check_levels(
            consumed_capacity,
            "emcvnx_consumed_capacity",
            None,
            infoname="Consumed capacity",
            human_readable_func=render.bytes,
        )

    avail_capacity_raw = data.get("%s_Available Capacity (GBs)" % tier_name)
    if avail_capacity_raw is not None:
        avail_capacity = float(avail_capacity_raw) * 1024**3
        yield check_levels(
            avail_capacity,
            "emcvnx_avail_capacity",
            None,
            infoname="Available capacity",
            human_readable_func=render.bytes,
        )

    percent_subscribed_raw = data.get("%s_Percent Subscribed" % tier_name)
    if percent_subscribed_raw is not None:
        percent_subscribed = float(percent_subscribed_raw.replace("%", ""))
        yield check_levels(
            percent_subscribed,
            "emcvnx_perc_subscribed",
            None,
            infoname="Percent subscribed",
            human_readable_func=render.percent,
        )

    for direction in ("for Higher", "for Lower", "Within"):
        value_raw = data.get(f"{tier_name}_Data Targeted {direction} Tier (GBs)")
        if value_raw is not None:
            value = float(value_raw) * 1024**3
            short_dir = direction.split()[-1].lower()
            yield check_levels(
                value,
                "emcvnx_targeted_%s" % short_dir,
                None,
                infoname="Move %s" % short_dir,
                human_readable_func=render.bytes,
            )


check_info["emcvnx_storage_pools.tieringtypes"] = LegacyCheckDefinition(
    service_name="Pool %s tiering",
    sections=["emcvnx_storage_pools"],
    discovery_function=inventory_emcvnx_storage_pools_tieringtypes,
    check_function=check_emcvnx_storage_pools_tieringtypes,
)

# .
#   .--deduplication-------------------------------------------------------.
#   |        _          _             _ _           _   _                  |
#   |     __| | ___  __| |_   _ _ __ | (_) ___ __ _| |_(_) ___  _ __       |
#   |    / _` |/ _ \/ _` | | | | '_ \| | |/ __/ _` | __| |/ _ \| '_ \      |
#   |   | (_| |  __/ (_| | |_| | |_) | | | (_| (_| | |_| | (_) | | | |     |
#   |    \__,_|\___|\__,_|\__,_| .__/|_|_|\___\__,_|\__|_|\___/|_| |_|     |
#   |                          |_|                                         |
#   '----------------------------------------------------------------------'


def _emcvnx_get_text_perf(data, key, perfname, format_func=render.bytes, factor=1024**3):
    field = data.get(key, "unknown")
    try:
        value = float(field) * factor
        return format_func(value), [(perfname, value)]
    except ValueError:
        return str(field), []


def check_emcvnx_storage_pools_deduplication(item, _no_params, parsed):
    if not (data := parsed.get(item)):
        return
    yield 0, "State: %s" % data.get("Deduplication State", "unknown")
    yield 0, "Status: %s" % data.get("Deduplication Status", "unknown").split("(")[0]
    yield 0, "Rate: %s" % data.get("Deduplication Rate", "unknown")

    txt, perf = _emcvnx_get_text_perf(
        data, "Efficiency Savings (GBs)", "emcvnx_dedupl_efficiency_savings"
    )
    yield 0, "Efficiency savings: %s" % txt, perf

    txt, perf = _emcvnx_get_text_perf(
        data,
        "Deduplication Percent Completed",
        "emcvnx_dedupl_perc_completed",
        format_func=render.percent,
        factor=1.0,
    )
    yield 0, "Percent completed: %s" % txt, perf

    txt, perf = _emcvnx_get_text_perf(
        data, "Deduplication Remaining Size (GBs)", "emcvnx_dedupl_remaining_size"
    )
    yield 0, "Remaining size: %s" % txt, perf

    txt, perf = _emcvnx_get_text_perf(
        data, "Deduplication Shared Capacity (GBs)", "emcvnx_dedupl_shared_capacity"
    )
    yield 0, "Shared capacity: %s" % txt, perf


check_info["emcvnx_storage_pools.deduplication"] = LegacyCheckDefinition(
    service_name="Pool %s Deduplication",
    sections=["emcvnx_storage_pools"],
    discovery_function=inventory_emcvnx_storage_pools,
    check_function=check_emcvnx_storage_pools_deduplication,
)
