#!/usr/bin/env python3

import re
from hashlib import md5
from sys import argv, exit

# Supress SSL certificate warnings for ssl_verify=False
import urllib3
from lxml import html
from requests import Session

USERNAME_FIELD = "g2"
PASSWORD_FIELD = "g3"
CRSF_FIELD = "password"

STATUS_OK = 0
STATUS_WARNING = 1
STATUS_CRITICAL = 2
STATUS_UNKNOWN = 3


class OMMCrawler:
    def __init__(self, hostname, username, password):
        self.session = Session()
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        self.session.verify = False

        self.url = f"https://{hostname}"
        self.login_data = {
            USERNAME_FIELD: username,
            PASSWORD_FIELD: password,
            CRSF_FIELD: md5(password.encode()).hexdigest(),
        }
        self.logged_in = False

    def login(self):
        # if we have multiple dect masters, find out which one is the current master
        current_master_url = self.session.get(self.url, verify=False).url
        self.hostname = re.search(r"^(.*[\\\/])", current_master_url).group(0)[:-1]

        response = self.session.post(f"{self.url}/login_set.html", data=self.login_data)
        response.raise_for_status()

        # set cookie
        pass_value = re.search(r"(?<=pass=)\d+(?=;)", response.text).group(0)
        self.session.cookies.set("pass", pass_value)
        self.logged_in = True

    def get_station_status(self):
        if not self.logged_in:
            self.login()

        data = {}
        response = self.session.get(f"{self.url}/fp_pnp_status.html")
        response.raise_for_status()
        tree = html.fromstring(response.text)
        xpath_results = tree.xpath('//tr[@class="l0" or @class="l1"]')

        for result in xpath_results:
            bubble_is_in_inactive_cluster = False
            bubble_is_connected = False
            bubble_is_active = False

            bubble_name = result.xpath("td[4]/text()")[0]
            try:
                bubble_is_connected = result.xpath("td[11]/img/@alt")[0] == "yes"

                if bubble_is_connected:
                    try:
                        bubble_is_active = result.xpath("td[12]/img/@alt")[0] == "yes"
                    except IndexError:
                        # If an IndexError occurs, there is no image in the
                        # 12th td. This means this bubble is in the not inside
                        # an active DECT cluster, but is a backup bubble.
                        # This is probably fine.
                        bubble_is_active = False
                        bubble_is_in_inactive_cluster = True
                else:
                    bubble_is_active = False
            except:
                # There is no Image in the 11th td. This usually means there
                # is a warning message in the 10th td. We do not care about
                # that, currently.
                pass

            data[bubble_name] = {
                "is_connected": bubble_is_connected,
                "is_active": bubble_is_active,
                "is_in_inactive_cluster": bubble_is_in_inactive_cluster,
            }
        return data

    def handle_station_data(self):
        try:
            data = self.get_station_status()
        except Exception as e:
            print(f"Something went wrong. You should take a look at {self.url}")
            print(repr(e))
            exit(STATUS_UNKNOWN)

        critical = False
        for name, status in data.items():
            if not status["is_active"] and not status["is_connected"]:
                print(
                    f"Base station {name} is not active or connected! Check manually!"
                )
                critical = True
            elif not status["is_active"] and not status["is_in_inactive_cluster"]:
                # Bubble is part of an active DECT cluster, but not active.
                # This shouldn't happen.
                print(
                    f"Base station {name} is not active but connected! Check manually!"
                )
                critical = True
            elif not status["is_connected"]:
                # This should never happen. Seeing this state means OMM
                # itself is broken.
                print(
                    f"Base station {name} is not connected but active! Check manually!"
                )
                critical = True

        if critical:
            exit(STATUS_CRITICAL)
        else:
            print(f"OK - {len(data)} base stations connected")
            exit(STATUS_OK)


if __name__ == "__main__":
    omm = OMMCrawler(argv[1], argv[2], argv[3])
    omm.handle_station_data()