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

import ast
import re

# params = {
#     "mincount": (tuple, integer),
#     "maxcount": -"-,
#     "minage_oldest": (tuple, seconds),
#     "maxage_oldest":  -"-,
#     "minage_newest": -"-,
#     "maxage_newest": -"-,
#     "minsize_smallest": (tuple, bytes),
#     "maxsize_...
#     "minsize_largest": -"-,
#     "maxsize_...
# }

# 'additional_rules': [('/var/log/sys*', {'maxsize_largest': (1, 2)})]

# .
#   .--Parsing-------------------------------------------------------------.
#   |                  ____                _                               |
#   |                 |  _ \ __ _ _ __ ___(_)_ __   __ _                   |
#   |                 | |_) / _` | '__/ __| | '_ \ / _` |                  |
#   |                 |  __/ (_| | |  \__ \ | | | | (_| |                  |
#   |                 |_|   \__,_|_|  |___/_|_| |_|\__, |                  |
#   |                                              |___/                   |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'


def parse_filestats(info):
    sections_info = {}
    current = []  # should never be used, but better safe than sorry
    for line in info:
        if not line:
            continue
        if line[0].startswith("[[["):
            output_variety, subsection_name = line[0][3:-3].split(None, 1)
            current = sections_info.setdefault((output_variety, subsection_name), [])
        else:
            current.append(line[0])

    return {
        name: (variety, _parse_filestats_load_lines(v))
        for (variety, name), v in sections_info.items()
        if v
    }


def _parse_filestats_load_lines(info):
    list_of_dicts = []
    for line in info:
        try:
            list_of_dicts.append(ast.literal_eval(line))
        except SyntaxError:
            pass
    return list_of_dicts


# .
#   .--Helpers-------------------------------------------------------------.
#   |                  _   _      _                                        |
#   |                 | | | | ___| |_ __   ___ _ __ ___                    |
#   |                 | |_| |/ _ \ | '_ \ / _ \ '__/ __|                   |
#   |                 |  _  |  __/ | |_) |  __/ |  \__ \                   |
#   |                 |_| |_|\___|_| .__/ \___|_|  |___/                   |
#   |                              |_|                                     |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'


def check_filestats_count(count, params, show_files, reported_lines):
    """common check result - used by main and count_only check"""
    levels = params.get("maxcount", (None, None)) + params.get("mincount", (None, None))
    result = check_levels(
        count,
        "file_count",
        levels,
        infoname="Files in total",
        human_readable_func=lambda i: "%d" % i,
    )
    state, info, perf = result
    if not show_files or state == 0:
        return result

    details = f"{info}\n" + "\n".join([f"[{l['path']}]" for l in reported_lines if l.get("path")])
    return (state, details, perf)


def check_filestats_extremes(files, params, show_files=False):
    """common check result - used by main and extremes_only check"""
    if not files:
        return []
    long_output = {}
    for key, hr_function, minlabel, maxlabel in (
        ("size", get_bytes_human_readable, "smallest", "largest"),
        ("age", get_age_human_readable, "newest", "oldest"),
    ):
        files.sort(key=lambda f: f.get(key))  # pylint: disable=cell-var-from-loop
        for efile, label in ((files[0], minlabel), (files[-1], maxlabel)):
            levels = params.get("max%s_%s" % (key, label), (None, None)) + params.get(
                "min%s_%s" % (key, label), (None, None)
            )
            yield check_levels(
                efile[key],
                None,
                levels,
                infoname=label.title(),
                human_readable_func=hr_function,
            )

        if not show_files:
            continue

        min_label_levels = params.get("max%s_%s" % (key, minlabel), (None, None)) + params.get(
            "min%s_%s" % (key, minlabel), (None, None)
        )
        for efile in files:
            state, _text, _no_perf = check_levels(
                efile[key],
                None,
                min_label_levels,
            )
            if state == 0:
                break
            if efile["path"] not in long_output:
                text = "Age: %s, Size: %s%s" % (
                    get_age_human_readable(efile["age"]),
                    get_bytes_human_readable(efile["size"]),
                    state_markers[state],
                )
                long_output[efile["path"]] = text

        max_label_levels = params.get("max%s_%s" % (key, maxlabel), (None, None)) + params.get(
            "min%s_%s" % (key, maxlabel), (None, None)
        )
        for efile in reversed(files):
            state, _text, _no_perf = check_levels(
                efile[key],
                None,
                max_label_levels,
            )
            if state == 0:
                break
            if efile["path"] not in long_output:
                text = "Age: %s, Size: %s%s" % (
                    get_age_human_readable(efile["age"]),
                    get_bytes_human_readable(efile["size"]),
                    state_markers[state],
                )
                long_output[efile["path"]] = text

    return ["[%s] %s" % key_text for key_text in sorted(long_output.items())]


# .
#   .--Checks--------------------------------------------------------------.
#   |                    ____ _               _                            |
#   |                   / ___| |__   ___  ___| | _____                     |
#   |                  | |   | '_ \ / _ \/ __| |/ / __|                    |
#   |                  | |___| | | |  __/ (__|   <\__ \                    |
#   |                   \____|_| |_|\___|\___|_|\_\___/                    |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'


@get_parsed_item_data
def check_filestats(_item, params, data):

    _output_variety, reported_lines = data
    sumry = [s for s in reported_lines if s.get("type") == "summary"]
    count = sumry[0].get("count", None) if sumry else None

    # only WARN/CRIT files are shown
    show_files = bool(params.get("show_all_files", False))

    if count is not None:
        yield check_filestats_count(count, params, show_files, reported_lines)

    files = [i for i in reported_lines if i.get("type") == "file"]

    if not files:
        return

    # only WARN/CRIT files are shown
    show_files = bool(params.get("show_all_files", False))

    additional_rules = params.get("additional_rules", {})

    matching_files = {}
    remaining_files = []
    for efile in files:
        for display_name, file_expression, rules in additional_rules:
            if re.match(file_expression, efile["path"]):
                matching_files.setdefault(
                    file_expression,
                    {
                        "display_name": display_name,
                        "rules": rules,
                        "file_list": [],
                    },
                )["file_list"].append(efile)
                break
        else:
            remaining_files.append(efile)

    remaining_files_output = yield from check_filestats_extremes(
        remaining_files,
        params,
        show_files,
    )

    if additional_rules:
        yield 0, "Additional rules enabled"

    remaining_files_count = count  # for display in service details

    for file_expression, file_details in matching_files.items():
        file_list = file_details["file_list"]
        file_count = len(file_list)
        remaining_files_count -= file_count
        yield 0, "\n%s" % file_details["display_name"]
        yield 0, "Pattern: %r" % file_expression
        yield 0, "Files in total: %d" % file_count
        output = yield from check_filestats_extremes(
            file_list,
            file_details["rules"],
            show_files,
        )
        yield 0, "\n".join(output)

    if additional_rules:
        yield 0, "\nRemaining files: %d" % remaining_files_count

    yield 0, "\n" + "\n".join(remaining_files_output)


@get_parsed_item_data
def check_filestats_single(_item, params, data):

    _output_variety, reported_lines = data
    if len(reported_lines) != 1:
        yield 1, "Received multiple filestats per single file service. Please check agent plugin configuration (mk_filestats)."

    single_stat = [i for i in reported_lines if i.get("type") == "file"][0]
    for key, hr_function in (("size", get_bytes_human_readable), ("age", get_age_human_readable)):

        yield check_levels(
            single_stat.get(key),
            key if key == "size" else None,
            (
                params.get("max_%s" % key, (None, None))[0],
                params.get("max_%s" % key, (None, None))[1],
                params.get("min_%s" % key, (None, None))[0],
                params.get("min_%s" % key, (None, None))[1],
            ),
            human_readable_func=hr_function,
            infoname=key.title(),
        )


@discover
def discover_filestats(key, data):
    return data[0] != "single_file"


@discover
def discover_filestats_single(key, data):
    return data[0] == "single_file"


check_info["filestats.single"] = {
    "inventory_function": discover_filestats_single,
    "check_function": check_filestats_single,
    "service_description": "File %s",
    "has_perfdata": True,
    "group": "filestats_single",
}

check_info["filestats"] = {
    "parse_function": parse_filestats,
    "inventory_function": discover_filestats,
    "check_function": check_filestats,
    "service_description": "File group %s",
    "has_perfdata": True,
    "group": "filestats",
}
