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

import lib.args
import lib.base
import lib.human
import lib.infomaniak
import lib.lftest
import lib.time
import lib.txt
from lib.globals import STATE_CRIT, STATE_OK, STATE_UNKNOWN, STATE_WARN

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

DESCRIPTION = """Checks each backup device (slot) across all Infomaniak Swiss Backup products via the
Infomaniak API. Alerts when storage usage exceeds the configured thresholds or when
a device reports an error state. Devices can be filtered by customer, name, tag, or
user."""

DEFAULT_CRIT = 95
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_SEVERITY = 'warn'
DEFAULT_TIMEOUT = 8
DEFAULT_WARN = 90


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(
        '--account-id',
        help='Infomaniak account ID.',
        dest='ACCOUNT_ID',
        required=True,
    )

    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=lib.args.help('--critical') + ' Default: >= %(default)s',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

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

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

    parser.add_argument(
        '--ignore-customer',
        help='Any device whose product customer name matches this Python regex will be ignored. '
        'Can be specified multiple times. '
        'Example: `--ignore-customer "(?i)test"`.',
        action='append',
        default=None,
        dest='IGNORE_CUSTOMER',
    )

    parser.add_argument(
        '--ignore-name',
        help='Any device whose name matches this Python regex will be ignored. '
        'Can be specified multiple times. '
        'Example: `--ignore-name "(?i)old-backup"`.',
        action='append',
        default=None,
        dest='IGNORE_NAME',
    )

    parser.add_argument(
        '--ignore-tag',
        help='Any device whose product tag matches this Python regex will be ignored. '
        'Can be specified multiple times. '
        'Example: `--ignore-tag "(?i)deprecated"`.',
        action='append',
        default=None,
        dest='IGNORE_TAG',
    )

    parser.add_argument(
        '--ignore-user',
        help='Any device whose username matches this Python regex will be ignored. '
        'Can be specified multiple times. '
        'Example: `--ignore-user "(?i)testuser"`.',
        action='append',
        default=None,
        dest='IGNORE_USER',
    )

    parser.add_argument(
        '--severity',
        help='Severity for alerting on locked devices. Default: %(default)s',
        dest='SEVERITY',
        default=DEFAULT_SEVERITY,
        choices=['warn', 'crit'],
    )

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

    parser.add_argument(
        '--token',
        help='Infomaniak API token.',
        dest='TOKEN',
        required=True,
    )

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

    parser.add_argument(
        '-w',
        '--warning',
        help=lib.args.help('--warning') + ' 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)

    # set default values for append parameters that were not specified
    if args.IGNORE_CUSTOMER is None:
        args.IGNORE_CUSTOMER = []
    if args.IGNORE_NAME is None:
        args.IGNORE_NAME = []
    if args.IGNORE_TAG is None:
        args.IGNORE_TAG = []
    if args.IGNORE_USER is None:
        args.IGNORE_USER = []

    # fetch data
    if args.TEST is None:
        slots = lib.base.coe(
            lib.infomaniak.get_swiss_backup_slots(
                args.ACCOUNT_ID,
                args.TOKEN,
                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)
        slots = json.loads(stdout)

    # init some vars
    msg = ''
    state = STATE_OK
    perfdata = ''
    table_data = []
    compiled_ignore_customer = [re.compile(item) for item in args.IGNORE_CUSTOMER]
    compiled_ignore_name = [re.compile(item) for item in args.IGNORE_NAME]
    compiled_ignore_tag = [re.compile(item) for item in args.IGNORE_TAG]
    compiled_ignore_user = [re.compile(item) for item in args.IGNORE_USER]

    # analyze data
    for slot in slots:
        for slot_data in slot.get('data'):
            # ignore devices matching any --ignore-* parameter
            if any(
                item.search(slot.get('product_customer_name', ''))
                for item in compiled_ignore_customer
            ):
                continue
            if any(
                item.search(slot_data.get('customer_name', ''))
                for item in compiled_ignore_name
            ):
                continue
            if any(
                item.search(tag['name'])
                for item in compiled_ignore_tag
                for tag in slot.get('product_tags', [])
            ):
                continue
            if any(
                item.search(slot_data.get('username', ''))
                for item in compiled_ignore_user
            ):
                continue
            if slot_data.get('storage_used', 0):
                used = round(
                    slot_data.get('storage_used') / slot_data.get('size') * 100, 1
                )
                used_state = lib.base.get_state(used, args.WARN, args.CRIT)
            else:
                # Swiss Backup ordered / paid, but not used / no backups found - throw a warning
                used = 0
                used_state = STATE_WARN
            state = lib.base.get_worst(state, used_state)

            if args.SEVERITY == 'warn':
                locked_state = lib.base.get_state(
                    slot_data.get('locked', False),
                    True,
                    None,
                    _operator='eq',
                )
            else:
                locked_state = lib.base.get_state(
                    slot_data.get('locked', False),
                    None,
                    True,
                    _operator='eq',
                )
            state = lib.base.get_worst(state, locked_state)

            perfdata += lib.base.get_perfdata(
                f'{slot_data.get("id")}-locked',
                int(slot_data.get('locked', False)),
                _min=0,
            )
            perfdata += lib.base.get_perfdata(
                f'{slot_data.get("id")}-percent',
                used,
                uom='%',
                warn=args.WARN,
                crit=args.CRIT,
                _min=0,
                _max=100,
            )
            perfdata += lib.base.get_perfdata(
                f'{slot_data.get("id")}-total',
                slot_data.get('size'),
                uom='B',
                _min=0,
                _max=slot_data.get('size'),
            )
            perfdata += lib.base.get_perfdata(
                f'{slot_data.get("id")}-usage',
                slot_data.get('storage_used'),
                uom='B',
                _min=0,
                _max=slot_data.get('size'),
            )

            if slot_data.get('usage_last_check') is None:
                slot_data['usage_last_check'] = 0

            table_data.append(
                {
                    'activate_notifications': slot_data.get('activate_notifications'),
                    'connection_type': slot_data.get('connection_type'),
                    'created_at': slot_data.get('created_at'),
                    'customer_name': slot_data.get('customer_name'),
                    'id': slot_data.get('id'),
                    'lang': slot_data.get('lang'),
                    'locked': (
                        f'{slot_data.get("locked", False)}'
                        f'{lib.base.state2str(locked_state, prefix=" ")}'
                    ),
                    'product_customer_name': slot.get('product_customer_name'),
                    'product_tags': ', '.join(
                        [i['name'] for i in slot.get('product_tags')]
                    ),
                    'subtype': slot_data.get('subtype'),
                    'type': slot_data.get('type'),
                    'unit_admin': slot_data.get('unit_admin'),
                    'usage_last_check': (
                        f'{lib.human.seconds2human(lib.time.now(as_type="epoch") - slot_data["usage_last_check"])}'
                        f' ago'
                    ),
                    'used': (
                        f'{lib.human.bytes2human(slot_data.get("storage_used"))}'
                        f' / '
                        f'{lib.human.bytes2human(slot_data.get("size"))}'
                    ),
                    'used_percent': f'{used}%{lib.base.state2str(used_state, prefix=" ")}',
                    'username': slot_data.get('username'),
                }
            )

    # build the message
    if table_data:
        keys = [
            'id',
            'product_customer_name',
            'product_tags',
            'username',
            'customer_name',
            'connection_type',
            'locked',
            'usage_last_check',
            'used',
            'used_percent',
        ]
        headers = [
            'ID',
            'Customer',
            'Tag',
            'User',
            'Name',
            'Type',
            'Locked',
            'Usage Upd.',
            'Used',
            'Used %',
        ]
        # sort table by user tags
        msg += lib.base.get_table(
            sorted(table_data, key=lambda d: d['product_tags']),
            keys,
            header=headers,
        )

    if state == STATE_CRIT:
        msg = 'There are critical errors.\n\n' + msg
    elif state == STATE_WARN:
        msg = 'There are warnings.\n\n' + msg
    else:
        msg = 'Everything is ok.\n\n' + msg

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


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