#!/usr/bin/python3
#
# Home Assistant sensor plugin for Nagios/Icinga
#
# (c) Gerrit Beine, 2019-2023
#

import argparse
import requests
import json
import time
import sys
import re

status = 0
message = ''
args = {}

nagios_codes = [ 'OK', 'WARNING', 'CRITICAL', 'UNKNOWN' ]

parser = argparse.ArgumentParser()
parser.add_argument('-H', '--host', metavar="<hostname>", help="host to connect to (defaults to localhost)", dest='host', default="localhost")
parser.add_argument('-P', '--port', metavar="<port>", help="network port to connect to (defaults to 8123)", dest='port', default=8123, type=int)
parser.add_argument('-t', '--token', metavar="<token>", help="API token (defaults to None)", dest='token', default=None)
parser.add_argument('-s', '--sensor', metavar="<sensor>", help="Sensor id (default to None)", dest='sensor', default=None)
parser.add_argument('-S', '--ssl', help="use HTTPS", dest='ssl', default=False, action='store_true')
parser.add_argument('-I', '--int', help="Use state as int", dest='int', default=False, action='store_true')
parser.add_argument('-F', '--float', help="Use state as float", dest='float', default=False, action='store_true')
parser.add_argument('-T', '--timestamp', help="Use state as timestamp", dest='timestamp', default=False, action='store_true')
parser.add_argument('-R', '--regex', help="Use expected or notexpected value as regular expression", dest='regex', default=False, action='store_true')
parser.add_argument('-e', '--expected', help="Check expected state (default to None)", dest='expected', default=None)
parser.add_argument('-n', '--notexpected', help="Check not expected state (default to None)", dest='notexpected', default=None)
parser.add_argument('-w', '--warning', help="Check if above threshold (warning, default to None)", dest='warning', default=None, type=float)
parser.add_argument('-c', '--critical', help="Check if above threshold (critical, default to None)", dest='critical', default=None, type=float)
parser.add_argument('-W', '--warning-below', help="Check if below threshold (warning, default to None)", dest='bwarning', default=None, type=float)
parser.add_argument('-C', '--critical-below', help="Check if below threshold (critical, default to None)", dest='bcritical', default=None, type=float)
parser.add_argument('-r', '--range', help="Check if in range [from:to] (default to None)", dest='range', default=None)
parser.add_argument('-v', '--verbose', help="Verbose mode: show the response data", dest='verbose', default=False, action='store_true')

args = parser.parse_args()

if (args.warning and args.bwarning) or (args.critical and args.bcritical):
    print("Please specify only values for checking above or below thresholds.")
    sys.exit(3)
if args.warning and not args.critical:
    print("Checking for threshold requires values for --warning and --cricital.")
    sys.exit(3)
if not args.warning and args.critical:
    print("Checking for threshold requires values for --warning and --cricital.")
    sys.exit(3)
if args.bwarning and not args.bcritical:
    print("Checking for threshold requires values for --warning-below and --cricital-below.")
    sys.exit(3)
if not args.bwarning and args.bcritical:
    print("Checking for threshold requires values for --warning-below and --cricital-below.")
    sys.exit(3)
if args.range:
    borders = args.range.split(":")
    if len(borders) != 2:
        print("Checking for a range requires two numbers, e.g. '10:23'")
        sys.exit(3)
    args.range_upper = float(borders[1])
    args.range_lower = float(borders[0])
if args.regex:
    if args.notexpected:
        args.pattern = re.compile(args.notexpected)
    else:
        args.pattern = re.compile(args.expected)

if args.ssl:
    url = "https"
else:
    url = "http"

url += "://" + args.host + ":" + str(args.port) + "/api/"

if args.sensor:
    url += "states/" + args.sensor

headers = {
    "Authorization": "Bearer " + args.token,
}

response = requests.request("GET", url, headers=headers)

data = json.loads(response.text)

if args.int or args.float:
    if args.int:
        state = int(data['state'])
    elif args.float:
        state = float(data['state'])

    # test if value is above thresholds
    if args.warning and args.warning > state:
        status = 0
        message = "State is {}".format(data['state'])
    elif args.critical and args.critical > state:
        status = 1
        message = "State {} is above {}".format(data['state'], args.warning)
    elif args.critical and args.critical <= state:
        status = 2
        message = "State {} is above {}".format(data['state'], args.critical)

    # test if value is below thresholds
    elif args.bwarning and args.bwarning < state:
        status = 0
        message = "State is {}".format(data['state'])
    elif args.bcritical and args.bcritical < state:
        status = 1
        message = "State {} is below {}".format(data['state'], args.bwarning)
    elif args.bcritical and args.bcritical >= state:
        status = 2
        message = "State {} is below {}".format(data['state'], args.bcritical)

    # test if value is in range
    elif args.range:
        if args.range_lower <= state <= args.range_upper:
            status = 0
            message = "State is {}".format(data['state'])
        else:
            status = 2
            message = "State {} is out of range {}".format(data['state'], args.range)

    else:
        status = 3
        message = "No thresholds or range defined fo state {}".format(data['state'])

elif args.timestamp:
    time = int(time.time())
    if args.expected is None:
        interval = 20
    else:
        interval = int(args.expected)
    if int(data['state']) > time - interval:
        status = 0
        message = "Timestamp is {}".format(data['state'])
    else:
        status = 2
        message = "Timestamp is {}, expected {} {}".format(data['state'], time, interval)

elif args.notexpected:
    if (args.pattern and re.match(args.pattern, data['state'])) or (data['state'] != args.notexpected):
        status = 0
        message = "State is {}".format(data['state'])
    else:
        status = 2
        message = "State is {}, not expected {}".format(data['state'], args.notexpected)

elif args.expected:
    if (args.pattern and re.match(args.pattern, data['state'])) or (data['state'] == args.expected):
        status = 0
        message = "State is {}".format(data['state'])
    else:
        status = 2
        message = "State is {}, expected {}".format(data['state'], args.expected)

else:
    status = 3
    message = "No expectations specified for {}".format(data['state'])

print("{} - {}".format(nagios_codes[status], message))

if args.verbose:
    print(data)

sys.exit(status)

