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

import lib.args
import lib.base
import lib.cache
import lib.human
import lib.time
import lib.txt
import lib.url
from lib.globals import STATE_OK, STATE_UNKNOWN

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

DESCRIPTION = """Retrieves recent activity and usage statistics from a Metabase instance via its API.
Reports active users, executed queries, dashboards, and other operational metrics.
Credentials are cached to reduce API calls.
The figures are reported for trending and never alert on their own."""

DEFAULT_CACHE_EXPIRE = (
    13 * 24 + 23
)  # hours; in Metabase, by default, sessions are good for 14 days
DEFAULT_CRIT = 90
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_TIMEOUT = 8
DEFAULT_URL = 'http://localhost:3000'
DEFAULT_USERNAME = 'metabase-admin'
DEFAULT_WARN = 80


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',
        help='Time after which the credential cache expires, in hours. '
        'Default: %(default)s',
        dest='CACHE_EXPIRE',
        type=int,
        default=DEFAULT_CACHE_EXPIRE,
    )

    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-perfdata',
        help=lib.args.help('--no-perfdata'),
        dest='NO_PERFDATA',
        action='store_true',
        default=False,
    )

    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='Password for authenticating against the Metabase API.',
        dest='PASSWORD',
        required=True,
    )

    parser.add_argument(
        '--proxy',
        help=lib.args.help('--proxy'),
        dest='PROXY',
        default=None,
    )

    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='Base URL of the Metabase instance. Default: %(default)s',
        dest='URL',
        default=DEFAULT_URL,
    )

    parser.add_argument(
        '--username',
        help='Username for authenticating against the Metabase API. '
        'Default: %(default)s',
        dest='USERNAME',
        default=DEFAULT_USERNAME,
    )

    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 get_token(metabase_url, user, password, expire):
    """Gets an API token from Metabase, using your credentials.
    Equivalent to:

    $ curl -X POST \\
    $     -H "Content-Type: application/json" \\
    $     -d '{"username": "person@metabase.com", "password": "linuxfabrik"}' \\
    $     http://localhost:3000/api/session
    """
    # we cache credentials to reuse them until they expire, because logins are rate-limited for security
    token = lib.cache.get(
        'metabase-token', filename='linuxfabrik-monitoring-plugins-metabase-stats.db'
    )
    if token:
        return (True, token)

    metabase_url += '/api/session'
    header = {
        'Content-Type': 'application/json',
    }
    data = {
        'username': user,
        'password': password,
    }
    success, result = lib.url.fetch_json(
        metabase_url,
        header=header,
        data=data,
        encoding='serialized-json',
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        proxy=args.PROXY,
        timeout=args.TIMEOUT,
    )
    if not success:
        return (success, result)
    if not result:
        return (False, f'There was no result from {metabase_url}.')
    if 'id' not in result:
        return (False, 'Something went wrong, maybe wrong username/password.')
    token = result['id']
    lib.cache.set(
        'metabase-token',
        token,
        lib.time.now() + expire,
        filename='linuxfabrik-monitoring-plugins-metabase-stats.db',
    )
    return (True, token)


def get_data(metabase_url, token, data={}):
    header = {
        'Content-Type': 'application/json',
        'X-Metabase-Session': token,
    }
    return lib.url.fetch(
        metabase_url,
        header=header,
        data=data,
        encoding='serialized-json',
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        proxy=args.PROXY,
        timeout=args.TIMEOUT,
    )


def get_json(metabase_url, token, data={}):
    header = {
        'Content-Type': 'application/json',
        'X-Metabase-Session': token,
    }
    return lib.url.fetch_json(
        metabase_url,
        header=header,
        data=data,
        encoding='serialized-json',
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        proxy=args.PROXY,
        timeout=args.TIMEOUT,
    )


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)

    # init some vars
    msg = ''
    state = STATE_OK
    perfdata = ''

    token = lib.base.coe(
        get_token(args.URL, args.USERNAME, args.PASSWORD, args.CACHE_EXPIRE * 60 * 60)
    )

    # fetch data
    activity = lib.base.coe(get_json(args.URL + '/api/activity', token))[0]

    settings = lib.base.coe(
        get_data(args.URL + '/api/setting/site-name', token)
    )  # /api/setting/:key returns plain text, not json
    stats = lib.base.coe(get_json(args.URL + '/api/util/stats', token))

    # build the message
    msg += f'{settings} on '
    msg += f'Metabase {stats["version"]}; '

    msg += f'{stats["stats"]["user"]["users"]["total"]} {lib.txt.pluralize("user", stats["stats"]["user"]["users"]["total"])}, '
    msg += f'{stats["stats"]["database"]["databases"]["analyzed"]} {lib.txt.pluralize("DB", stats["stats"]["database"]["databases"]["analyzed"])} analyzed, '
    msg += f'{stats["stats"]["question"]["questions"]["gui"]} {lib.txt.pluralize("question", stats["stats"]["question"]["questions"]["gui"])} (GUI), '
    msg += f'{stats["stats"]["alert"]["alerts"]} {lib.txt.pluralize("alert", stats["stats"]["alert"]["alerts"])}, '
    msg += f'{stats["stats"]["pulse"]["pulses"]} {lib.txt.pluralize("pulse", stats["stats"]["pulse"]["pulses"])}, '
    msg += f'{stats["stats"]["collection"]["collections"]} {lib.txt.pluralize("collection", stats["stats"]["collection"]["collections"])}; '
    msg += f'{stats["stats"]["system"]["processors"]} CPUs, {stats["stats"]["system"]["max_memory"]} MiB RAM'

    detail_name = (
        '/' + activity['details']['name'] if 'name' in activity['details'] else ''
    )
    time_ago = lib.human.seconds2human(
        lib.time.timestrdiff(
            lib.time.now(as_type='iso'),
            activity['timestamp'].replace('T', ' '),
        ),
    )
    msg += (
        f'\nLast activity:'
        f' "{activity["topic"]}{detail_name}"'
        f' by {activity["user"]["common_name"]}'
        f' ({time_ago} ago)'
    )

    perfdata += lib.base.get_perfdata(
        'alerts',
        stats['stats']['alert']['alerts'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'collections',
        stats['stats']['collection']['collections'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'cpu',
        stats['stats']['system']['processors'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'dbs_analyzed',
        stats['stats']['database']['databases']['total'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'pulses',
        stats['stats']['pulse']['pulses'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'questions_gui',
        stats['stats']['question']['questions']['gui'],
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'ram',
        stats['stats']['system']['max_memory'],
        uom='MB',
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'users',
        stats['stats']['user']['users']['total'],
        _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()
