#!/usr/lib64/linuxfabrik-monitoring-plugins/venv/bin/python
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details."""

import argparse
import os
import sys

import lib.args
import lib.base
import lib.disk
import lib.human
import lib.time
from lib.globals import STATE_OK, STATE_UNKNOWN, STATE_WARN

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2026071201'

DESCRIPTION = """Checks the status of the last borgbackup run by parsing the borg logfile. Alerts on
non-zero return codes from the create or prune steps, and warns if the last successful
backup is older than a configurable threshold (default: 24 hours). Also detects
active borg mounts in /proc/mounts.
Requires root or sudo."""

DEFAULT_CRIT = None
DEFAULT_WARN = 24


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(
        description=DESCRIPTION,
        epilog=lib.args.epilog(__file__),
        formatter_class=lib.args.HelpFormatter,
    )

    parser.add_argument(
        '-V',
        '--version',
        action='version',
        version=f'%(prog)s: v{__version__} by {__author__}',
    )

    parser.add_argument(
        '--always-ok',
        help=lib.args.help('--always-ok'),
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '-c',
        '--critical',
        help='CRIT threshold for the time since the last backup started, in hours. '
        'Default: %(default)s',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '--no-perfdata',
        help=lib.args.help('--no-perfdata'),
        dest='NO_PERFDATA',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '-w',
        '--warning',
        help='WARN threshold for the time since the last backup started, in hours. '
        'Default: %(default)s',
        dest='WARN',
        type=int,
        default=DEFAULT_WARN,
    )

    args, _ = parser.parse_known_args()
    return args


def main():
    """The main function. This is where the magic happens."""

    # parse the command line
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    perfdata = ''
    path = '/var/log/borg/borg.log'

    # content of the log file:
    # start: 2020-04-08 23:00:01
    # prune_retc: 0
    # create_retc: 0
    # end: 2020-04-08 23:00:13

    if os.path.exists(path) <= 0 and not lib.disk.file_exists(path, allow_empty=True):
        lib.base.cu(f'Logfile {path} not found or empty.')

    mount_lines = ''
    for line in lib.base.coe(lib.disk.read_file('/proc/mounts')).splitlines(
        keepends=True
    ):
        if 'borgfs' in line:
            mount_lines += '* ' + line

    if mount_lines:
        lib.base.oao(
            f'There are active borg mounts.\n\n{mount_lines}',
            STATE_WARN,
            always_ok=args.ALWAYS_OK,
        )

    starttime = None
    endtime = None
    create_retc = None
    prune_retc = None
    for line in lib.base.coe(lib.disk.read_file(path)).splitlines():
        if line.startswith('start'):
            starttime = line.split(': ')[1].strip()

        if line.startswith('end'):
            endtime = line.split(': ')[1].strip()

        if line.startswith('create_retc'):
            create_retc = int(line.split(': ')[1])

        if line.startswith('prune_retc'):
            prune_retc = int(line.split(': ')[1])

    if any(v is None for v in (starttime, endtime, create_retc, prune_retc)):
        lib.base.cu('Could not find all expected values in the logfile.')

    # We ignore retc 1, as it means operation reached its normal end,
    # but there were warnings from borg.
    state = STATE_OK
    create_retc_state = lib.base.get_state(create_retc, 2, None, _operator='ge')
    state = lib.base.get_worst(state, create_retc_state)
    perfdata += lib.base.get_perfdata(
        'create_retc',
        create_retc,
        warn=1,
        crit=2,
        _min=0,
    )

    prune_retc_state = lib.base.get_state(prune_retc, 2, None, _operator='ge')
    state = lib.base.get_worst(state, prune_retc_state)
    perfdata += lib.base.get_perfdata(
        'prune_retc',
        prune_retc,
        warn=1,
        crit=2,
        _min=0,
    )

    now = lib.time.now(as_type='iso')
    delta = lib.time.timestrdiff(now, starttime)

    last_starttime_state = lib.base.get_state(
        delta,
        args.WARN * 60 * 60 if args.WARN is not None else None,
        args.CRIT * 60 * 60 if args.CRIT is not None else None,
    )
    state = lib.base.get_worst(state, last_starttime_state)

    duration = lib.time.timestrdiff(endtime, starttime)  # in seconds
    perfdata += lib.base.get_perfdata(
        'duration',
        duration,
        uom='s',
        _min=0,
    )

    # build the message
    msg = (
        f'Last Backup started {starttime}, ended {endtime},'
        f' took {lib.human.seconds2human(duration)}.\n'
    )
    if state != STATE_OK:
        if last_starttime_state != STATE_OK:
            msg = 'Last backup is too long ago. ' + msg
        else:
            msg = 'One or more errors. ' + msg
    msg += f'* Create retc: {create_retc}, State: {lib.base.state2str(create_retc_state)}\n'
    msg += f'* Prune retc: {prune_retc}, State: {lib.base.state2str(prune_retc_state)}'

    # over and out
    lib.base.oao(
        msg, state, perfdata, always_ok=args.ALWAYS_OK, no_perfdata=args.NO_PERFDATA
    )


if __name__ == '__main__':
    try:
        main()
    except Exception:
        lib.base.cu()
