#!/usr/bin/env python3
"""
Icinga plugin to check if a machine is currently reporting its metrics into InfluxDB remote host
"""

import argparse
import logging
import os
import re
import sys
from getpass import getpass

import influxdb

parser = argparse.ArgumentParser(description=__doc__)

parser.add_argument("-H", "--host", help="Host")
parser.add_argument("-e", "--eeid", help="EeID")
parser.add_argument(
    "-i", "--inactive-threshold", type=int, help="Inactive threshold (in minutes)", default=5
)


log_opts = parser.add_argument_group("Logging options")
log_opts.add_argument("-d", "--debug", action="store_true", help="Show debug messages")

log_opts.add_argument("-v", "--verbose", action="store_true", help="Show verbose messages")

log_opts.add_argument("-w", "--warning", action="store_true", help="Show warning messages")

log_opts.add_argument("-l", "--logfile", action="store", type=str, help="Log file path")

log_opts.add_argument(
    "-C",
    "--console",
    action="store_true",
    help="Also log on console (even if log file is provided)",
)

influxdb_opts = parser.add_argument_group("InfluxDB options")
influxdb_opts.add_argument("-I", "--influxdb-host", help="InfluxDB host", default="localhost")
influxdb_opts.add_argument("-p", "--influxdb-port", help="InfluxDB port", default=8086)
influxdb_opts.add_argument("-S", "--influxdb-ssl", action="store_true", help="Enable SSL")
influxdb_opts.add_argument(
    "-X", "--influxdb-no-ssl-verify", action="store_false", help="Disable SSL verify"
)
influxdb_opts.add_argument("-u", "--influxdb-username", help="InfluxDB username")
influxdb_opts.add_argument("-P", "--influxdb-password", help="InfluxDB password")
influxdb_opts.add_argument(
    "-f",
    "--influxdb-password-file",
    help="InfluxDB password file",
    default="/etc/influxdb/.eemonitoring",
)
influxdb_opts.add_argument(
    "-F",
    "--influxdb-credentials-file",
    help="InfluxDB credentials file (content format: 'user:password')",
    default=f"/etc/.{os.path.basename(sys.argv[0])}",
)
influxdb_opts.add_argument("-D", "--influxdb-database", help="InfluxDB database name")
influxdb_opts.add_argument(
    "-m",
    "--influxdb-measurement",
    help="InfluxDB measurement (default='cpu')",
    default="cpu",
)
influxdb_opts.add_argument(
    "-k",
    "--influxdb-key",
    help="InfluxDB key to use with InfluxDB measurement (default='host')",
    default="host",
)
influxdb_opts.add_argument(
    "-V",
    "--influxdb-value",
    help="InfluxDB value to use with InfluxDB measurement (default='usage_user')",
    default="usage_user",
)

opts = parser.parse_args()

# Initialize log
log = logging.getLogger()
logformat = logging.Formatter(
    "%(asctime)s - " + os.path.basename(sys.argv[0]) + " - %(levelname)s : %(message)s"
)

if opts.debug:
    log.setLevel(logging.DEBUG)
elif opts.verbose:
    log.setLevel(logging.INFO)
elif opts.warning:
    log.setLevel(logging.WARNING)
else:
    log.setLevel(logging.FATAL)

if opts.logfile:
    logfile = logging.FileHandler(opts.logfile)
    logfile.setFormatter(logformat)
    log.addHandler(logfile)

if not opts.logfile or opts.console:
    logconsole = logging.StreamHandler()
    logconsole.setFormatter(logformat)
    log.addHandler(logconsole)

# Enable warnings capture by Python logging to avoid displaying urllib warnings
# if logging is not enabled
logging.captureWarnings(True)

# Check provided username
if not opts.influxdb_username:
    if opts.influxdb_credentials_file and os.path.exists(opts.influxdb_credentials_file):
        try:
            with open(opts.influxdb_credentials_file, encoding="utf8") as fd:
                content = fd.read().strip().split(":")
            opts.influxdb_username = content.pop(0)
            opts.influxdb_password = ":".join(content)
        except Exception as err:  # pylint: disable=broad-exception-caught
            parser.exit(
                3,
                "UNKNOWN - Fail to load InfluxDB credentials from file "
                f"'{opts.influxdb_credentials_file}': {err}"
                "\n",
            )
            sys.exit(3)
        if not opts.influxdb_username or not opts.influxdb_password:
            parser.exit(
                3,
                "UNKNOWN - Empty InfluxDB username or password loaded from "
                f"credentials file ({opts.influxdb_credentials_file}). "
                "Please check it or provide it using -u/--username and "
                "-p/--password parameters.\n",
            )
        log.debug("InfluxDB credentials loaded from file '%s'", opts.influxdb_credentials_file)
    else:
        parser.exit(
            3,
            "UNKNOWN - You must provide InfluxDB username using -u/--influxdb-username "
            "parameter.\n",
        )

if not opts.influxdb_database and not opts.eeid:
    if opts.host:
        m = re.match(r"^([a-z0-9]{2,3})-(.+)$", opts.host)
        if m:
            opts.eeid = m.group(1)
            opts.host = m.group(2)
        else:
            parser.exit(
                3,
                "UNKNOWN - Fail to detect EeID from hostname {opts.host}. Please provide it using "
                "-e/--eeid parameter.\n",
            )
    else:
        parser.exit(
            3,
            "UNKNOWN - You must provide InfluxDB database using -D/--database parameter or EeID "
            "using -e/--eeid parameter.\n",
        )

if not opts.influxdb_password:
    if opts.influxdb_password_file and os.path.exists(opts.influxdb_password_file):
        try:
            with open(opts.influxdb_password_file, encoding="utf8") as fd:
                opts.influxdb_password = fd.read().rstrip()
        except Exception as err:  # pylint: disable=broad-exception-caught
            parser.exit(
                3,
                "UNKNOWN - Fail to load InfluxDB password from file "
                f"'{opts.influxdb_password_file}': {err}"
                "\n",
            )
            sys.exit(3)
        if not opts.influxdb_password:
            parser.exit(
                3,
                "UNKNOWN - Empty InfluxDB password loaded from password file "
                f"({opts.influxdb_password_file}). Please check it or "
                "provide it using -p/--password parameter.\n",
            )
            log.debug(
                "InfluxDB password loaded from password file '%s'", opts.influxdb_password_file
            )
    elif opts.influxdb_credentials_file and os.path.exists(opts.influxdb_credentials_file):
        try:
            with open(opts.influxdb_credentials_file, encoding="utf8") as fd:
                content = fd.read().strip().split(":")
            username = content.pop(0)
            assert (
                username == opts.influxdb_username
            ), "Username from credentials file does not match with provided one"
            opts.influxdb_password = ":".join(content)
        except Exception as err:  # pylint: disable=broad-exception-caught
            parser.exit(
                3,
                "UNKNOWN - Fail to load InfluxDB credentials from file "
                f"'{opts.influxdb_credentials_file}': {err}"
                "\n",
            )
            sys.exit(3)
        if not opts.influxdb_password:
            parser.exit(
                3,
                "UNKNOWN - Empty InfluxDB password loaded from credentials"
                f"file ({opts.influxdb_credentials_file}). Please check it or "
                "provide it using -p/--password parameters.\n",
            )
        log.debug(
            "InfluxDB password loaded from credentials file '%s'", opts.influxdb_credentials_file
        )

    else:
        opts.influxdb_password = getpass(
            f"Please enter password for user {opts.influxdb_username}: "
        )

# Check/compute InfluxDB database name
if not opts.influxdb_database:
    # We already have check that eeid is provided
    opts.influxdb_database = "eeservers" if opts.eeid == "ee" else f"cst_{opts.eeid}"

# Clean hostname if it start by EeID
if opts.host and opts.eeid and opts.host.startswith(f"{opts.eeid}-"):
    opts.host = opts.host[len(f"{opts.eeid}-") :]

client = influxdb.InfluxDBClient(
    host=opts.influxdb_host,
    port=opts.influxdb_port,
    ssl=opts.influxdb_ssl,
    verify_ssl=opts.influxdb_no_ssl_verify is not False,
    username=opts.influxdb_username,
    password=opts.influxdb_password,
    database=opts.influxdb_database,
)

# Retrieve all hosts from DB
result = client.query(
    f"show tag values from {opts.influxdb_measurement} with key = {opts.influxdb_key}"
)
hosts = [item["value"] for item in result.get_points(measurement=opts.influxdb_measurement)]
log.debug("Hosts: %s", ", ".join(hosts))

if opts.host:
    if opts.host not in hosts:
        print(
            f"CRITICAL - Host {opts.host} is unknown in InfluxDB database {opts.influxdb_database}"
        )
        sys.exit(2)

# Retrieve all active hosts from DB
result = client.query(
    rf"select count({opts.influxdb_value}) from {opts.influxdb_measurement} "
    rf"where time > now() - {opts.inactive_threshold}m "
    rf"group by {opts.influxdb_key}"
)
active_hosts = [tags[opts.influxdb_key] for m, tags in result.keys()]
log.debug("Active hosts: %s", ", ".join(active_hosts))

if opts.host:
    if opts.host not in active_hosts:
        print(f"WARNING - Host {opts.host} is inactive since more than {opts.inactive_threshold}m")
        sys.exit(1)
    print(f"OK - Host {opts.host} was active in last {opts.inactive_threshold}m")
    sys.exit(0)

inactive_hosts = [host for host in hosts if host not in active_hosts]
if inactive_hosts:
    print(
        f"WARNING - {len(inactive_hosts)} inactive hosts since more than {opts.inactive_threshold}m"
    )
    print("\n".join(inactive_hosts))
    sys.exit(1)
print(f"OK - {len(hosts)} active hosts in last {opts.inactive_threshold}m")
sys.exit(0)
