#!/usr/bin/env python3
# Signalgrid
# Bulk: no

import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request


API_URL = "https://api.signalgrid.co/v1/push"


def notify(name, default=""):
    return os.environ.get(f"NOTIFY_{name}", default)


def fail(message):
    print(f"Signalgrid: {message}")
    sys.exit(2)


def get_password_parameter(name):
    mode = notify(f"PARAMETER_{name}_1")
    source = notify(f"PARAMETER_{name}_2")
    store_id = notify(f"PARAMETER_{name}_3_1")
    explicit_value = notify(f"PARAMETER_{name}_3_2")

    if mode != "cmk_postprocessed":
        fail(f"invalid password parameter format for {name}")

    if source == "explicit_password":
        if not explicit_value:
            fail(
                f"missing explicit "
                f"{name.lower().replace('_', ' ')}"
            )

        return explicit_value

    if source == "stored_password":
        if not store_id:
            fail(f"missing password store ID for {name}")

        try:
            from cmk.utils import password_store
            return password_store.lookup(store_id)

        except Exception as error:
            fail(
                f"could not read "
                f"{name.lower().replace('_', ' ')} "
                f"from password store: {error}"
            )

    fail(f"unknown password source '{source}' for {name}")


def get_parameters():
    client_key = get_password_parameter("CLIENT_KEY")

    channel = notify("PARAMETER_CHANNEL")

    critical_alerts = notify(
        "PARAMETER_CRITICAL_ALERTS",
        "False",
    )

    if not channel:
        fail("missing channel token")

    critical_alerts_enabled = (
        critical_alerts.strip().lower()
        in ("1", "true", "yes", "on")
    )

    return (
        client_key,
        channel,
        critical_alerts_enabled,
    )


def get_notification():
    hostname = notify("HOSTNAME")
    host_alias = notify("HOSTALIAS")

    host_state = notify("HOSTSTATE").upper()
    host_output = notify("HOSTOUTPUT")

    service = notify("SERVICEDESC")
    service_state = notify("SERVICESTATE").upper()
    service_output = notify("SERVICEOUTPUT")

    notification_type = notify("NOTIFICATIONTYPE").upper()

    if service:
        state = service_state

        title = f"{state} · {hostname}: {service}"

        body = (
            service_output
            or f"Service state changed to {state}"
        )

    else:
        state = host_state

        host_name = (
            hostname
            or host_alias
            or "Checkmk"
        )

        title = f"{state} · {host_name}"

        body = (
            host_output
            or f"Host state changed to {state}"
        )

    if notification_type.startswith("RECOVERY"):
        severity = "SUCCESS"

    elif notification_type.startswith("DOWNTIMESTART"):
        severity = "INFO"

    elif notification_type.startswith("DOWNTIMEEND"):
        severity = "INFO"

    elif notification_type.startswith("DOWNTIMECANCELLED"):
        severity = "WARN"

    elif notification_type.startswith("ACKNOWLEDGEMENT"):
        severity = "INFO"

    elif notification_type.startswith("FLAPPINGSTART"):
        severity = "WARN"

    elif notification_type.startswith("FLAPPINGSTOP"):
        severity = "SUCCESS"

    elif notification_type.startswith("CUSTOM"):
        severity = "INFO"

    else:
        severity_map = {
            "OK": "SUCCESS",
            "UP": "SUCCESS",

            "WARN": "WARN",
            "WARNING": "WARN",

            "CRIT": "CRIT",
            "CRITICAL": "CRIT",

            "DOWN": "CRIT",

            "UNREACH": "WARN",
            "UNREACHABLE": "WARN",

            "UNKNOWN": "INFO",
        }

        severity = severity_map.get(
            state,
            "INFO",
        )

    return (
        title,
        body,
        severity,
    )


def send():
    (
        client_key,
        channel,
        critical_alerts_enabled,
    ) = get_parameters()

    (
        title,
        body,
        severity,
    ) = get_notification()

    payload = {
        "client_key": client_key,
        "channel": channel,
        "type": severity,
        "title": title,
        "body": body,
    }

    if critical_alerts_enabled:
        payload["critical"] = "true"

    data = urllib.parse.urlencode(
        payload
    ).encode("utf-8")

    request = urllib.request.Request(
        API_URL,
        data=data,
        headers={
            "Content-Type":
                "application/x-www-form-urlencoded",
            "User-Agent":
                "Signalgrid-Checkmk/1.0",
        },
        method="POST",
    )

    try:
        with urllib.request.urlopen(
            request,
            timeout=10,
        ) as response:

            response_body = response.read().decode(
                "utf-8",
                errors="replace",
            )

            if not (200 <= response.status < 300):
                fail(
                    f"HTTP {response.status}: "
                    f"{response_body}"
                )

            try:
                result = json.loads(response_body)

            except json.JSONDecodeError:
                fail(
                    "invalid JSON response from Signalgrid: "
                    f"{response_body}"
                )

            api_code = str(
                result.get("code", "")
            )

            api_text = str(
                result.get("text", "")
            )

            ruuid = str(
                result.get("ruuid", "")
            )

            if api_code != "200":
                fail(
                    f"API error {api_code}: "
                    f"{api_text}"
                )

            print(
                "Signalgrid notification sent successfully "
                f"(ruuid={ruuid})"
            )

    except urllib.error.HTTPError as error:
        response_body = error.read().decode(
            "utf-8",
            errors="replace",
        )

        fail(
            f"HTTP {error.code}: "
            f"{response_body}"
        )

    except urllib.error.URLError as error:
        fail(
            f"connection failed: "
            f"{error.reason}"
        )

    except Exception as error:
        fail(str(error))


if __name__ == "__main__":
    send()
