#!/usr/bin/env python3
# -*- 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 json
import sys

import lib.args
import lib.base
import lib.human
import lib.lftest
import lib.txt
import lib.url
import lib.veeam
from lib.globals import STATE_CRIT, STATE_OK, STATE_UNKNOWN, STATE_WARN

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

DESCRIPTION = """Monitors Veeam Backup & Replication via PowerShell, checking for failed VMs and jobs,
jobs running longer than expected, and backup repository usage. Also reports backup
infrastructure component status and recent job results."""

DEFAULT_CRIT = 90
DEFAULT_FAILED_JOB_RUNS = 0
DEFAULT_FAILED_VM_LASTEST_STATES = 0
DEFAULT_INSECURE = True
DEFAULT_MAX_BACKUP_JOB_DURATION = 86400
DEFAULT_MAX_REPLICA_JOB_DURATION = 86400
DEFAULT_NO_PROXY = False
DEFAULT_TIMEOUT = 3
DEFAULT_URL = 'https://localhost:9398'
DEFAULT_USERNAME = 'Administrator'
DEFAULT_WARN = 80
DEFAULT_WARNING_VM_LASTEST_STATES = 0
DEFAULT_WARNINGS_JOB_RUNS = 0


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    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 backup repository usage as a percentage. '
        'Default: >= %(default)s',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '--failed-job-runs',
        help='Veeam threshold for `FailedJobRuns`. Default: > %(default)s.',
        dest='FAILED_JOB_RUNS',
        type=int,
        default=DEFAULT_FAILED_JOB_RUNS,
    )

    parser.add_argument(
        '--failed-vm-lastest-states',
        help='Veeam threshold for `FailedVmLastestStates`. Default: > %(default)s.',
        dest='FAILED_VM_LASTEST_STATES',
        type=int,
        default=DEFAULT_FAILED_VM_LASTEST_STATES,
    )

    parser.add_argument(
        '--insecure',
        help=lib.args.help('--insecure'),
        dest='INSECURE',
        action='store_true',
        default=DEFAULT_INSECURE,
    )

    parser.add_argument(
        '--max-backup-job-duration',
        help='Maximum allowed backup job duration in seconds. Default: > %(default)s.',
        dest='MAX_BACKUP_JOB_DURATION',
        type=int,
        default=DEFAULT_MAX_BACKUP_JOB_DURATION,
    )

    parser.add_argument(
        '--max-replica-job-duration',
        help='Maximum allowed replica job duration in seconds. Default: > %(default)s.',
        dest='MAX_REPLICA_JOB_DURATION',
        type=int,
        default=DEFAULT_MAX_REPLICA_JOB_DURATION,
    )

    parser.add_argument(
        '--no-proxy',
        help=lib.args.help('--no-proxy'),
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '-p',
        '--password',
        help='Veeam REST API password.',
        dest='PASSWORD',
        required=True,
    )

    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,
    )

    parser.add_argument(
        '--url',
        help='Veeam REST API URL. Default: %(default)s',
        dest='URL',
        default=DEFAULT_URL,
    )

    parser.add_argument(
        '--username',
        help='Veeam REST API username. Default: %(default)s',
        dest='USERNAME',
        default=DEFAULT_USERNAME,
        required=True,
    )

    parser.add_argument(
        '-w',
        '--warning',
        help='WARN threshold for backup repository usage as a percentage. '
        'Default: >= %(default)s',
        dest='WARN',
        type=int,
        default=DEFAULT_WARN,
    )

    parser.add_argument(
        '--warnings-job-runs',
        help='Veeam threshold for `WarningsJobRuns`. Default: > %(default)s.',
        dest='WARNINGS_JOB_RUNS',
        type=int,
        default=DEFAULT_WARNINGS_JOB_RUNS,
    )

    parser.add_argument(
        '--warning-vm-lastest-states',
        help='Veeam threshold for `WarningVmLastestStates`. Default: > %(default)s.',
        dest='WARNING_VM_LASTEST_STATES',
        type=int,
        default=DEFAULT_WARNING_VM_LASTEST_STATES,
    )

    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)

    # fetch data
    if args.TEST is None:
        success, result = lib.veeam.get_token(args)
        if not success:
            lib.base.cu(result)

        header = {
            'X-RestSvcSessionId': result['X-RestSvcSessionId'],
            'Accept': 'application/json',
        }
        result = {}

        # https://helpcenter.veeam.com/docs/backup/em_rest/reports_summary_overview.html?ver=110
        url = args.URL + '/api/reports/summary/overview'
        result['overview'] = lib.base.coe(
            lib.url.fetch_json(
                url,
                header=header,
                insecure=args.INSECURE,
                no_proxy=args.NO_PROXY,
                timeout=args.TIMEOUT,
            ),
        )

        # https://helpcenter.veeam.com/docs/backup/em_rest/reports_summary_statistics.html?ver=110
        url = args.URL + '/api/reports/summary/job_statistics'
        result['job_statistics'] = lib.base.coe(
            lib.url.fetch_json(
                url,
                header=header,
                insecure=args.INSECURE,
                no_proxy=args.NO_PROXY,
                timeout=args.TIMEOUT,
            ),
        )

        # https://helpcenter.veeam.com/docs/backup/em_rest/reports_summary_vms_overview.html?ver=110
        url = args.URL + '/api/reports/summary/vms_overview'
        result['vms_overview'] = lib.base.coe(
            lib.url.fetch_json(
                url,
                header=header,
                insecure=args.INSECURE,
                no_proxy=args.NO_PROXY,
                timeout=args.TIMEOUT,
            ),
        )

        # https://helpcenter.veeam.com/docs/backup/em_rest/reports_summary_repository.html?ver=110
        url = args.URL + '/api/reports/summary/repository'
        result['repository'] = lib.base.coe(
            lib.url.fetch_json(
                url,
                header=header,
                insecure=args.INSECURE,
                no_proxy=args.NO_PROXY,
                timeout=args.TIMEOUT,
            ),
        )
    else:
        # do not call the command, put in test data
        stdout, _stderr, _retc = lib.lftest.test(args.TEST)
        result = json.loads(stdout)

    # init some vars
    msg = ''
    state = STATE_OK
    perfdata = ''
    table_values = []

    # analyze data - "Lastets" (Veeam: sic!)

    table_values.append(
        {'key': 'BackedUpVms', 'value': result['vms_overview']['BackedUpVms']}
    )
    table_values.append(
        {'key': 'BackupServers', 'value': result['overview']['BackupServers']}
    )

    val = result['job_statistics']['FailedJobRuns']
    local_state = STATE_OK
    if val > args.FAILED_JOB_RUNS:
        local_state = STATE_CRIT
        state = lib.base.get_worst(state, local_state)
        msg += (
            f'{val} '
            f'{lib.txt.pluralize("Job", val)} failed'
            f'{lib.base.state2str(local_state, prefix=" ")}, '
        )
    table_values.append(
        {
            'key': 'FailedJobRuns',
            'value': str(result['job_statistics']['FailedJobRuns'])
            + lib.base.state2str(local_state, prefix=' '),
        }
    )

    val = result['overview']['FailedVmLastestStates']
    local_state = STATE_OK
    if val > args.FAILED_VM_LASTEST_STATES:
        local_state = STATE_CRIT
        state = lib.base.get_worst(state, local_state)
        msg += (
            f'{val} '
            f'{lib.txt.pluralize("VM", val)} failed'
            f'{lib.base.state2str(local_state, prefix=" ")}, '
        )
    table_values.append(
        {
            'key': 'FailedVmLastestStates',
            'value': str(result['overview']['FailedVmLastestStates'])
            + lib.base.state2str(local_state, prefix=' '),
        }
    )

    table_values.append(
        {
            'key': 'FullBackupPointsSize',
            'value': lib.human.bytes2human(
                result['vms_overview']['FullBackupPointsSize']
            ),
        }
    )
    table_values.append(
        {
            'key': 'IncrementalBackupPointsSize',
            'value': lib.human.bytes2human(
                result['vms_overview']['IncrementalBackupPointsSize']
            ),
        }
    )

    val = result['job_statistics']['MaxBackupJobDuration']
    local_state = STATE_OK
    if val > args.MAX_BACKUP_JOB_DURATION:
        # job lasts longer than 24 hours
        local_state = STATE_WARN
        state = lib.base.get_worst(state, local_state)
        msg += (
            f'"{result["job_statistics"]["MaxDurationBackupJobName"]}"'
            f' ran for {lib.human.seconds2human(val)}'
            f'{lib.base.state2str(local_state, prefix=" ")}, '
        )
    table_values.append(
        {
            'key': 'MaxBackupJobDuration',
            'value': lib.human.seconds2human(
                result['job_statistics']['MaxBackupJobDuration']
            )
            + lib.base.state2str(local_state, prefix=' '),
        }
    )

    table_values.append(
        {
            'key': 'MaxDurationBackupJobName',
            'value': result['job_statistics']['MaxDurationBackupJobName'],
        }
    )
    table_values.append(
        {
            'key': 'MaxDurationReplicaJobName',
            'value': result['job_statistics']['MaxDurationReplicaJobName'],
        }
    )
    table_values.append(
        {
            'key': 'MaxJobDuration',
            'value': lib.human.seconds2human(
                result['job_statistics']['MaxJobDuration']
            ),
        }
    )

    val = result['job_statistics']['MaxReplicaJobDuration']
    local_state = STATE_OK
    if val > args.MAX_REPLICA_JOB_DURATION:
        # job lasts longer than 24 hours
        local_state = STATE_WARN
        state = lib.base.get_worst(state, local_state)
        msg += (
            f'{result["job_statistics"]["MaxDurationReplicaJobName"]}'
            f' ran for {lib.human.seconds2human(val)}'
            f'{lib.base.state2str(local_state, prefix=" ")}, '
        )
    table_values.append(
        {
            'key': 'MaxReplicaJobDuration',
            'value': lib.human.seconds2human(
                result['job_statistics']['MaxReplicaJobDuration']
            )
            + lib.base.state2str(local_state, prefix=' '),
        }
    )

    table_values.append(
        {'key': 'ProtectedVms', 'value': result['vms_overview']['ProtectedVms']}
    )
    table_values.append(
        {'key': 'ProxyServers', 'value': result['overview']['ProxyServers']}
    )
    table_values.append(
        {
            'key': 'ReplicaRestorePointsSize',
            'value': lib.human.bytes2human(
                result['vms_overview']['ReplicaRestorePointsSize']
            ),
        }
    )
    table_values.append(
        {'key': 'ReplicatedVms', 'value': result['vms_overview']['ReplicatedVms']}
    )
    table_values.append(
        {'key': 'RepositoryServers', 'value': result['overview']['RepositoryServers']}
    )
    table_values.append(
        {'key': 'RestorePoints', 'value': result['vms_overview']['RestorePoints']}
    )
    table_values.append(
        {'key': 'RunningJobs', 'value': result['job_statistics']['RunningJobs']}
    )
    table_values.append(
        {
            'key': 'ScheduledBackupJobs',
            'value': result['job_statistics']['ScheduledBackupJobs'],
        }
    )
    table_values.append(
        {'key': 'ScheduledJobs', 'value': result['job_statistics']['ScheduledJobs']}
    )
    table_values.append(
        {
            'key': 'ScheduledReplicaJobs',
            'value': result['job_statistics']['ScheduledReplicaJobs'],
        }
    )
    table_values.append(
        {
            'key': 'SourceVmsSize',
            'value': lib.human.bytes2human(result['vms_overview']['SourceVmsSize']),
        }
    )
    table_values.append(
        {
            'key': 'SuccessBackupPercents',
            'value': str(result['vms_overview']['SuccessBackupPercents']) + '%',
        }
    )
    table_values.append(
        {
            'key': 'SuccessfulJobRuns',
            'value': result['job_statistics']['SuccessfulJobRuns'],
        }
    )
    table_values.append(
        {
            'key': 'SuccessfulVmLastestStates',
            'value': result['overview']['SuccessfulVmLastestStates'],
        }
    )
    table_values.append(
        {'key': 'TotalJobRuns', 'value': result['job_statistics']['TotalJobRuns']}
    )

    val = result['job_statistics']['WarningsJobRuns']
    local_state = STATE_OK
    if val > args.WARNINGS_JOB_RUNS:
        local_state = STATE_WARN
        state = lib.base.get_worst(state, local_state)
        msg += (
            f'{val} '
            f'{lib.txt.pluralize("Job", val)} with warnings'
            f'{lib.base.state2str(local_state, prefix=" ")}, '
        )
    table_values.append(
        {
            'key': 'WarningsJobRuns',
            'value': str(result['job_statistics']['WarningsJobRuns'])
            + lib.base.state2str(local_state, prefix=' '),
        }
    )

    val = result['overview']['WarningVmLastestStates']
    local_state = STATE_OK
    if val > args.WARNING_VM_LASTEST_STATES:
        local_state = STATE_WARN
        state = lib.base.get_worst(state, local_state)
        msg += (
            f'{val} '
            f'{lib.txt.pluralize("VM", val)} with warnings'
            f'{lib.base.state2str(local_state, prefix=" ")}, '
        )
    table_values.append(
        {
            'key': 'WarningVmLastestStates',
            'value': str(result['overview']['WarningVmLastestStates'])
            + lib.base.state2str(local_state, prefix=' '),
        }
    )

    for repo in result['repository']['Periods']:
        val = round(float(repo['BackupSize']) / float(repo['Capacity']) * 100, 1)
        local_state = lib.base.get_state(val, args.WARN, args.CRIT)
        state = lib.base.get_worst(state, local_state)
        msg += (
            f'"{repo["Name"]}" {val}%'
            f'{lib.base.state2str(local_state, prefix=" ")} '
            f'used - '
            f'total: {lib.human.bytes2human(repo["Capacity"])}, '
            f'used: {lib.human.bytes2human(repo["BackupSize"])}, '
            f'free: {lib.human.bytes2human(repo["FreeSpace"])}, '
        )
        perfdata += lib.base.get_perfdata(
            'Repo Usage ' + repo['Name'],
            val,
            uom='%',
            warn=args.WARN,
            crit=args.CRIT,
            _min=0,
            _max=100,
        )
        perfdata += lib.base.get_perfdata(
            'Repo Capacity ' + repo['Name'],
            repo['Capacity'],
            uom='B',
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            'Repo FreeSpace ' + repo['Name'],
            repo['FreeSpace'],
            uom='B',
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            'Repo BackupSize ' + repo['Name'],
            repo['BackupSize'],
            uom='B',
            _min=0,
        )

    # Build perfdata and property table
    perfdata += lib.base.get_perfdata(
        'BackedUpVms',
        result['vms_overview']['BackedUpVms'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'BackupServers',
        result['overview']['BackupServers'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'FailedJobRuns',
        result['job_statistics']['FailedJobRuns'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'FailedVmLastestStates',
        result['overview']['FailedVmLastestStates'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'FullBackupPointsSize',
        result['vms_overview']['FullBackupPointsSize'],
        uom='B',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'IncrementalBackupPointsSize',
        result['vms_overview']['IncrementalBackupPointsSize'],
        uom='B',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'MaxBackupJobDuration',
        result['job_statistics']['MaxBackupJobDuration'],
        uom='s',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'MaxJobDuration',
        result['job_statistics']['MaxJobDuration'],
        uom='s',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'MaxReplicaJobDuration',
        result['job_statistics']['MaxReplicaJobDuration'],
        uom='s',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ProtectedVms',
        result['vms_overview']['ProtectedVms'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ProxyServers',
        result['overview']['ProxyServers'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ReplicaRestorePointsSize',
        result['vms_overview']['ReplicaRestorePointsSize'],
        uom='B',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ReplicatedVms',
        result['vms_overview']['ReplicatedVms'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'RepositoryServers',
        result['overview']['RepositoryServers'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'RestorePoints',
        result['vms_overview']['RestorePoints'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'RunningJobs',
        result['job_statistics']['RunningJobs'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ScheduledBackupJobs',
        result['job_statistics']['ScheduledBackupJobs'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ScheduledJobs',
        result['job_statistics']['ScheduledJobs'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ScheduledReplicaJobs',
        result['job_statistics']['ScheduledReplicaJobs'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'SourceVmsSize',
        result['vms_overview']['SourceVmsSize'],
        uom='B',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'SuccessBackupPercents',
        result['vms_overview']['SuccessBackupPercents'],
        uom='%',
        _min=0,
        _max=100,
    )
    perfdata += lib.base.get_perfdata(
        'SuccessfulJobRuns',
        result['job_statistics']['SuccessfulJobRuns'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'SuccessfulVmLastestStates',
        result['overview']['SuccessfulVmLastestStates'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'TotalJobRuns',
        result['job_statistics']['TotalJobRuns'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'WarningsJobRuns',
        result['job_statistics']['WarningsJobRuns'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'WarningVmLastestStates',
        result['overview']['WarningVmLastestStates'],
        _min=0,
    )

    # build the message
    msg = (
        msg[:-2]
        + '\n\n'
        + lib.base.get_table(
            table_values,
            ['key', 'value'],
            header=['Key', 'Value'],
        )
    )

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


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