#!/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 Infomaniak Swiss Backup product details via the Infomaniak API. Alerts when
products are locked, maintenance window is active, or storage quota is exceeded.
Products can be filtered by customer or tag."""

DEFAULT_CRIT = 3  # days
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_SEVERITY = 'warn'
DEFAULT_TIMEOUT = 8
DEFAULT_WARN = 5  # days


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='CRIT threshold for the expiration date, in days. Default: %(default)s',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '--ignore-customer',
        help='Any product whose 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-tag',
        help='Any product whose 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(
        '--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(
        '--severity',
        help='Severity for alerting on locked, maintenance, or busy products. '
        '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='WARN threshold for the expiration date, in days. 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_TAG is None:
        args.IGNORE_TAG = []

    # fetch data
    if args.TEST is None:
        result = lib.base.coe(
            lib.infomaniak.get_swiss_backup_products(
                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, _, _ = lib.lftest.test(args.TEST)
        result = 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_tag = [re.compile(item) for item in args.IGNORE_TAG]

    # analyze data
    for product in result.get('data', {}):
        # ignore products matching any --ignore-* parameter
        if any(
            item.search(product.get('customer_name', ''))
            for item in compiled_ignore_customer
        ):
            continue
        if any(
            item.search(tag['name'])
            for item in compiled_ignore_tag
            for tag in product.get('tags', [])
        ):
            continue

        expired_at = product.get('expired_at') - lib.time.now(as_type='epoch')
        expired_at_state = lib.base.get_state(
            expired_at / 24 / 60 / 60, args.WARN, args.CRIT, _operator='le'
        )
        state = lib.base.get_worst(state, expired_at_state)

        if args.SEVERITY == 'warn':
            maintenance_state = lib.base.get_state(
                product.get('has_maintenance', False), True, None, _operator='eq'
            )
        else:
            maintenance_state = lib.base.get_state(
                product.get('has_maintenance', False), None, True, _operator='eq'
            )
        state = lib.base.get_worst(state, maintenance_state)

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

        if args.SEVERITY == 'warn':
            busy_state = lib.base.get_state(
                product.get('has_operation_in_progress', False),
                True,
                None,
                _operator='eq',
            )
        else:
            busy_state = lib.base.get_state(
                product.get('has_operation_in_progress', False),
                None,
                True,
                _operator='eq',
            )
        state = lib.base.get_worst(state, busy_state)

        perfdata += lib.base.get_perfdata(
            f'{product.get("id")}-busy',
            int(product.get('has_operation_in_progress', False)),
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            f'{product.get("id")}-locked',
            int(product.get('is_locked', False)),
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            f'{product.get("id")}-maintenance',
            int(product.get('has_maintenance', False)),
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            f'{product.get("id")}-size',
            product.get('size'),
            uom='B',
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            f'{product.get("id")}-storage_reserved',
            product.get('storage_reserved'),
            uom='B',
            _min=0,
        )

        busy_val = product.get('has_operation_in_progress', False)
        locked_val = product.get('is_locked', False)
        maint_val = product.get('has_maintenance', False)
        reserved = lib.human.bytes2human(
            product.get('storage_reserved'),
        )
        size = lib.human.bytes2human(product.get('size'))
        table_data.append(
            {
                'busy': (f'{busy_val}{lib.base.state2str(busy_state, prefix=" ")}'),
                'customer_name': product.get('customer_name'),
                'expires_in': (
                    f'{lib.human.seconds2human(expired_at)}'
                    f'{lib.base.state2str(expired_at_state, prefix=" ")}'
                ),
                'id': product.get('id'),
                'locked': (
                    f'{locked_val}{lib.base.state2str(locked_state, prefix=" ")}'
                ),
                'maintenance': (
                    f'{maint_val}{lib.base.state2str(maintenance_state, prefix=" ")}'
                ),
                'max_slots': product.get('max_slots'),
                'nb_slots': product.get('nb_slots'),
                'size': f'{reserved} / {size}',
                'tags': ', '.join([i['name'] for i in product.get('tags')]),
            }
        )

    # build the message
    if table_data:
        keys = [
            'id',
            'customer_name',
            'tags',
            'size',
            'nb_slots',
            'maintenance',
            'locked',
            'busy',
            'expires_in',
        ]
        headers = [
            'ID',
            'Customer',
            'Tag',
            'Size (alloc/avail)',
            'Dev',
            'Maint.',
            'Locked',
            'Busy',
            'Expires in',
        ]
        # sort table by user tags
        msg += lib.base.get_table(
            sorted(table_data, key=lambda d: d['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()
