#!/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 re
import sys

import lib.args
import lib.base
import lib.cache
import lib.lftest
import lib.shell
import lib.time
import lib.url
import lib.version
from lib.globals import STATE_OK, STATE_UNKNOWN, STATE_WARN

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

DESCRIPTION = """Checks if a newer version of mydumper is available by comparing the installed
version against the latest release on the GitHub releases API. Alerts when the installed
version is outdated."""

DEFAULT_CACHE_EXPIRE = 24  # hours
DEFAULT_PATH = 'mydumper'
DEFAULT_TIMEOUT = 8


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(
        '--cache-expire',
        # in hours, unlike lib.args.help('--cache-expire'), which says minutes
        help='The amount of time after which the cached latest release expires, '
        'in hours. '
        'Default: %(default)s',
        dest='CACHE_EXPIRE',
        type=int,
        default=DEFAULT_CACHE_EXPIRE,
    )

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

    parser.add_argument(
        '--path',
        help='Path to the mydumper binary. '
        'Resolved via $PATH when given without a directory. '
        'Default: %(default)s',
        dest='PATH',
        default=DEFAULT_PATH,
    )

    parser.add_argument(
        '--test',
        help=lib.args.help('--test'),
        dest='TEST',
        type=lib.args.csv,
    )

    parser.add_argument(
        '--timeout',
        help=lib.args.help('--timeout') + ' Default: %(default)s (seconds)',
        dest='TIMEOUT',
        type=int,
        default=DEFAULT_TIMEOUT,
    )

    args, _ = parser.parse_known_args()
    return args


def get_installed_version(
    path, test_arg=None, timeout=DEFAULT_TIMEOUT, always_ok=False
):
    if test_arg is None:
        cmd = [path, '--version']
        success, result = lib.shell.shell_exec(cmd, timeout=timeout)
        if not success:
            if result.startswith('Timeout after'):
                lib.base.oao(
                    f'Timeout after {timeout}s while running `{" ".join(cmd)}`.',
                    STATE_WARN,
                    always_ok=always_ok,
                )
            return ''
        stdout, _, _ = result
        stdout = stdout.strip()
    else:
        stdout, _, _ = lib.lftest.test(test_arg)
        stdout = stdout.strip()
    # where to find the version number in output?
    version_regex = r'mydumper(.*?),'
    try:
        stdout = re.search(version_regex, stdout)
        return stdout.group(1).strip()
    except Exception:
        return ''


def get_latest_version(expire, timeout=DEFAULT_TIMEOUT):
    # get version online, but first from cache
    latest_version = lib.cache.get('mydumper-version')
    if latest_version:
        return (True, latest_version)

    # nothing found in cache, get the latest version from github
    success, latest_version = lib.url.get_latest_version_from_github(
        'mydumper', 'mydumper', timeout=timeout
    )
    if not success:
        return (success, latest_version)
    if not latest_version:
        # GitHub answered, but named no release. Say so instead of caching the
        # empty answer and comparing the installed version against nothing.
        return (
            False,
            'GitHub reports no mydumper/myloader release to compare against.',
        )

    lib.cache.set('mydumper-version', latest_version, lib.time.now() + expire)
    return (True, latest_version)


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)

    # fetch data
    installed_version = get_installed_version(
        args.PATH, args.TEST, timeout=args.TIMEOUT, always_ok=args.ALWAYS_OK
    )
    if not installed_version:
        lib.base.cu(
            f'mydumper not found at `{args.PATH}`. Use --path to point at the binary.'
        )

    if args.TEST is not None:
        # In test mode, the second --test slot (the "stderr" slot of
        # lib.lftest.test) carries the pinned upstream latest version
        # so the test does not depend on a live GitHub fetch.
        _, stderr, _ = lib.lftest.test(args.TEST)
        latest_version = stderr.strip()
    else:
        latest_version = lib.base.coe(
            get_latest_version(args.CACHE_EXPIRE * 60 * 60, timeout=args.TIMEOUT)
        )

    # init some vars
    state = STATE_OK

    # analyze data
    if lib.version.version(installed_version) < lib.version.version(latest_version):
        state = STATE_WARN

    # build the message
    if state == STATE_OK:
        msg = f'mydumper/myloader {installed_version} is up to date'
    else:
        msg = (
            f'mydumper/myloader {installed_version}'
            f' installed, mydumper/myloader'
            f' {latest_version} available'
            f'{lib.base.state2str(state, prefix=" ")}'
        )
    perfdata = lib.base.get_perfdata(
        'mydumper-version',
        lib.version.version2float(installed_version),
        uom=None,
        _min=0,
    )

    # 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()
