#!/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.disk
import lib.human
import lib.lftest
import lib.time
import lib.txt
from lib.globals import STATE_OK, STATE_UNKNOWN

try:
    from swiftclient.service import SwiftService
except ImportError:
    print('Python module "python-swiftclient" is not installed.')
    sys.exit(STATE_UNKNOWN)


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

DESCRIPTION = """Checks OpenStack Swift object storage account statistics, including total container
count, object count, and bytes used. Alerts when storage usage exceeds the configured
thresholds."""

DEFAULT_CRIT = 10  # GiB free space left
DEFAULT_RC_FILE = '/var/spool/icinga2/.openstack.cnf'
DEFAULT_WARN = 50  # GiB free space left


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 remaining free space, in GiB. Default: <= %(default)s',
        dest='CRIT',
        type=int,
        default=DEFAULT_CRIT,
    )

    parser.add_argument(
        '--rc-file',
        help='Path to a rc file containing OpenStack connection parameters like OS_USERNAME '
        '(instead of specifying them on the command line). '
        'Example: `/var/spool/icinga2/.openstack.cnf`. '
        'Default: %(default)s',
        dest='RC_FILE',
        default=DEFAULT_RC_FILE,
    )

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

    parser.add_argument(
        '-w',
        '--warning',
        help='WARN threshold for remaining free space, in GiB. 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)

    # fetch data
    # https://github.com/openstack/python-swiftclient/tree/master/examples
    if args.TEST is None:
        # set a bunch of possible OpenStack credentials
        env = lib.base.coe(lib.disk.read_env(args.RC_FILE))
        _opts = {
            'auth_version': env.get('ST_AUTH_VERSION', 3),  # Should be '3'
            'object_dd_threads': 20,
            'os_auth_url': env.get('OS_AUTH_URL', None),
            'os_identity_api_version': env.get('OS_IDENTITY_API_VERSION', None),
            'os_interface': env.get('OS_INTERFACE', 'public'),
            'os_password': env.get('OS_PASSWORD', None),
            'os_project_domain_id': env.get('OS_PROJECT_DOMAIN_ID', None),
            'os_project_domain_name': env.get('OS_PROJECT_DOMAIN_NAME', 'default'),
            'os_project_id': env.get('OS_PROJECT_ID', None),
            'os_project_name': env.get('OS_PROJECT_NAME', None),
            'os_region_name': env.get('OS_REGION_NAME', None),
            'os_user_domain_name': env.get('OS_USER_DOMAIN_NAME', 'default'),
            'os_username': env.get('OS_USERNAME', None),
        }
        with SwiftService(options=_opts) as swift:
            # general statistics
            stat_account = swift.stat()
            if 'error' in stat_account and isinstance(stat_account['error'], Exception):
                lib.base.cu(str(stat_account['error']))
            # get stat for each container
            containers = {}
            for list_account_part in swift.list():
                for lap in list_account_part['listing']:
                    stat_container = swift.stat(container=lap['name'])
                    if stat_container['success']:
                        containers[lap['name']] = stat_container['headers']
    else:
        # do not call the command, put in test data
        # general statistics
        stat_account = json.loads(
            lib.base.coe(lib.disk.read_file(f'{args.TEST[0]}-stat_account'))
        )
        # get stat for each container
        containers = {}
        list_account_part = json.loads(
            lib.base.coe(lib.disk.read_file(f'{args.TEST[0]}-list_account_part'))
        )
        for lap in list_account_part['listing']:
            stat_container = json.loads(
                lib.base.coe(
                    lib.disk.read_file(f'{args.TEST[0]}-stat_container-{lap["name"]}')
                )
            )
            if stat_container['success']:
                containers[lap['name']] = stat_container['headers']

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

    # analyze data
    for name, container in containers.items():
        item = {
            'name': name,
            'accept-ranges': container.get('accept-ranges', None),
            'content-length': container.get('content-length', None),
            'content-type': container.get('content-type', None),
            'date': container.get('date', None),
            'last-modified': container.get('last-modified', None),
            'strict-transport-security': container.get(
                'strict-transport-security', None
            ),
            'vary': container.get('vary', None),
            'x-container-bytes-used': int(container.get('x-container-bytes-used', 0)),
            'x-container-meta-quota-bytes': int(
                container.get('x-container-meta-quota-bytes', 0)
            ),
            'x-container-object-count': lib.human.number2human(
                int(container.get('x-container-object-count', 0))
            ),
            'x-openstack-request-id': container.get('x-openstack-request-id', None),
            'x-storage-policy': container.get('x-storage-policy', None),
            'x-timestamp': container.get('x-timestamp', None),
            'x-trans-id': container.get('x-trans-id', None),
            'used': lib.human.bytes2human(
                int(container.get('x-container-bytes-used', 0))
            ),
            'quota': lib.human.bytes2human(
                int(container.get('x-container-meta-quota-bytes', 0))
            ),
        }
        if item['x-container-meta-quota-bytes']:
            # quota is set
            container_state = lib.base.get_state(
                item['x-container-meta-quota-bytes'] - item['x-container-bytes-used'],
                args.WARN * 1024 * 1024 * 1024,
                args.CRIT * 1024 * 1024 * 1024,
                'le',
            )
            state = lib.base.get_worst(state, container_state)
            free_bytes = max(
                0,
                item['x-container-meta-quota-bytes'] - item['x-container-bytes-used'],
            )
            item['free'] = (
                f'{lib.human.bytes2human(free_bytes)}'
                f'{lib.base.state2str(container_state, prefix=" ")}'
            )
            percent = round(
                float(item['x-container-bytes-used'])
                / float(item['x-container-meta-quota-bytes'])
                * 100,
                1,
            )
            item['used'] += f' ({percent}%)'
        else:
            item['free'] = ''
        table_data.append(item)

        perfdata += lib.base.get_perfdata(
            f'{name}_items',
            container.get('x-container-object-count', 0),
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            f'{name}_used',
            container.get('x-container-bytes-used', 0),
            uom='B',
            _min=0,
        )

    # build the message
    msg += 'Account: '
    if 'x-account-container-count' in stat_account['headers'] and int(
        stat_account['headers']['x-account-container-count']
    ):
        cnt = stat_account['headers']['x-account-container-count']
        msg += f'{cnt} {lib.txt.pluralize("container", int(cnt))}, '
    if 'x-account-object-count' in stat_account['headers'] and int(
        stat_account['headers']['x-account-object-count']
    ):
        obj_cnt = stat_account['headers']['x-account-object-count']
        msg += (
            f'{lib.human.number2human(obj_cnt)}'
            f' {lib.txt.pluralize("object", int(obj_cnt))}, '
        )
    if 'x-account-bytes-used' in stat_account['headers'] and int(
        stat_account['headers']['x-account-bytes-used']
    ):
        bytes_used = int(stat_account['headers']['x-account-bytes-used'])
        msg += f'{lib.human.bytes2human(bytes_used)} used, '
    if 'x-account-meta-quota-bytes' in stat_account['headers'] and int(
        stat_account['headers']['x-account-meta-quota-bytes']
    ):
        quota = int(stat_account['headers']['x-account-meta-quota-bytes'])
        msg += f'{lib.human.bytes2human(quota)} quota, '
    if msg.endswith(', '):
        msg = msg[:-2]

    if table_data:
        keys = [
            'name',
            'x-container-object-count',
            'quota',
            'used',
            'free',
        ]
        headers = [
            'Container',
            'Items',
            'Quota',
            'Used',
            'Free',
        ]
        msg += '\n\n' + lib.base.get_table(table_data, keys, header=headers)
    else:
        msg += '. Nothing checked.'

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


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