#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
import requests
import urllib3
import json
import argparse
from datetime import datetime, time, timedelta

urllib3.disable_warnings()

# =============================================================================
# Defaults
# =============================================================================
DEFAULT_FQDN = "pbs.autinity.de"
DEFAULT_API_TOKEN = ""

# =============================================================================
# CLI-Argumente (argparse)
# =============================================================================
parser = argparse.ArgumentParser(
    description="Check Proxmox Backup Server Tasks (getrennte Zeitfenster + altersbasierte Severity: frisch=kritisch)."
)
parser.add_argument("-d", "--debug", action="store_true", help="Debug-Ausgabe aktivieren")
parser.add_argument("-f", "--fqdn", default=DEFAULT_FQDN, help="PBS Server FQDN (default: %(default)s)")
parser.add_argument("-t", "--token", default=DEFAULT_API_TOKEN,
                    help="API Token im Format 'PBSAPIToken=<username>@pam!<api id>:<api secret>'")
# getrennte Zeitfenster
parser.add_argument("--since-output", type=int, default=7,
                    help="Zeitfenster in Tagen für AUSGABE/STATUS (default: 7)")
parser.add_argument("--since-graph", type=int, default=7,
                    help="Zeitfenster in Tagen für Grafana/Perfdata (default: 7)")
parser.add_argument("--crit-age-hours", type=int, default=24,
                    help="Fehler JÜNGER als diese Stunden => CRITICAL (default: 24)")
parser.add_argument("--warn-age-hours", type=int, default=72,
                    help="Fehler ÄLTER als crit und JÜNGER als diese Stunden => WARNING (default: 72 = 3 Tage)")

args = parser.parse_args()

debug = args.debug
fqdn_pbs = args.fqdn
api_token_pbs = args.token

if not fqdn_pbs or not api_token_pbs:
    print("FQDN und Token müssen angegeben werden.")
    sys.exit(3)

# =============================================================================
# Ausgabe-Puffer
# =============================================================================
taskoutput = []
processfailure = 0

def make_since_vars(days: int):
    dt = datetime.now() - timedelta(days=days)
    since_midnight = datetime.combine(dt.date(), time.min)
    return {"unix": int(since_midnight.timestamp()), "human": dt.strftime("%m/%d/%Y")}

SINCE_OUTPUT = make_since_vars(args.since_output)
SINCE_GRAPH  = make_since_vars(args.since_graph)

# Schwellwerte prüfen: warn muss > crit sein, sonst anheben
crit_age_hours = args.crit_age_hours
warn_age_hours = args.warn_age_hours
if warn_age_hours <= crit_age_hours:
    adjusted = crit_age_hours + 1
    taskoutput.append(
        f"Hinweis: warn-age-hours ({warn_age_hours}) <= crit-age-hours ({crit_age_hours}) – "
        f"setze warn-age-hours -> {adjusted}"
    )
    warn_age_hours = adjusted

# =============================================================================
# Helpers
# =============================================================================
def field(x): 
    return "" if x is None else str(x)

def fmt_ts(ts):
    try:
        return datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%d %H:%M:%S")
    except Exception:
        return "(n/a)"

def fmt_status(item):
    st = item.get("status")
    if st: return st
    if item.get("endtime") is None: return "(running)"
    return "(unknown)"

def task_timestamp(item):
    """Bevorzugt endtime, sonst starttime; gibt (datetime|None) zurück."""
    ts = item.get("endtime") or item.get("starttime")
    try:
        return datetime.fromtimestamp(int(ts)) if ts is not None else None
    except Exception:
        return None

# =============================================================================
# PBS API Query
# =============================================================================
def fetch_pbs_tasks(fqdn, status, apitoken, since_unix):
    """
    Ruft Tasks mit Statusfilter ab. Gibt (ok, msg, list[dict]) zurück.
    """
    try:
        resp = requests.get(
            f"https://{fqdn}:8007/api2/json/nodes/localhost/tasks",
            params={"limit": 0, "statusfilter": status, "since": since_unix},
            verify=False,
            timeout=5,
            headers={"Authorization": apitoken},
        )
    except requests.RequestException as e:
        return False, f"Request to https://{fqdn}:8007 failed: {e}", []

    if resp.status_code == 401:
        return False, f"Authentication failed for https://{fqdn}:8007", []
    if resp.status_code != 200:
        return False, f"Failed to access or process https://{fqdn}:8007 (HTTP {resp.status_code})", []

    try:
        jsondata = resp.json()
    except ValueError as e:
        return False, f"Invalid JSON from https://{fqdn}:8007: {e}", []

    if debug:
        print(json.dumps(jsondata, indent=2)); print("")

    return True, "", jsondata.get("data", [])

# =============================================================================
# Hauptlogik
# =============================================================================
taskoutput.append("")
taskoutput.append(
    f"Unsuccessful tasks at {fqdn_pbs} since {SINCE_OUTPUT['human']} "
    f"(AUSGABE-Fenster | CRIT < {crit_age_hours}h, WARN: {crit_age_hours}–{warn_age_hours}h):"
)
taskoutput.append("")

# --- AUSGABE/STATUS ---
ok, err, errors_out_list = fetch_pbs_tasks(fqdn_pbs, "error", api_token_pbs, SINCE_OUTPUT["unix"])
if not ok:
    taskoutput.append(err); taskoutput.append(""); processfailure += 1; errors_out_list = []

ok, err, warns_out_list = fetch_pbs_tasks(fqdn_pbs, "warning", api_token_pbs, SINCE_OUTPUT["unix"])
if not ok:
    taskoutput.append(err); taskoutput.append(""); processfailure += 1; warns_out_list = []

now = datetime.now()
pbs_errors_out = 0
pbs_warnings_out_api = 0
errors_crit_age = 0
errors_warn_age = 0

# Fehler-Details + Altersklassifizierung (umgekehrte Logik)
for i in errors_out_list:
    pbs_errors_out += 1
    taskoutput.append("worker_id: " + field(i.get("worker_id")))
    taskoutput.append("worker_type: " + field(i.get("worker_type")))
    taskoutput.append("user: " + field(i.get("user")))
    taskoutput.append("status: " + fmt_status(i))
    taskoutput.append("starttime: " + fmt_ts(i.get("starttime")))
    taskoutput.append("endtime: " + fmt_ts(i.get("endtime")))

    ts = task_timestamp(i)
    if ts is not None:
        age = now - ts
        age_hours = int(age.total_seconds() // 3600)
        if age_hours < crit_age_hours:
            errors_crit_age += 1
            taskoutput.append(f"age: ~{age_hours}h -> CRITICAL (jünger als {crit_age_hours}h)")
        elif age_hours < warn_age_hours:
            errors_warn_age += 1
            taskoutput.append(f"age: ~{age_hours}h -> WARNING (zwischen {crit_age_hours}h und {warn_age_hours}h)")
        else:
            taskoutput.append(f"age: ~{age_hours}h -> OK (älter als {warn_age_hours}h)")
    else:
        taskoutput.append("age: (n/a)")
    taskoutput.append("")

# API-Warnings zählen & ausgeben
for i in warns_out_list:
    pbs_warnings_out_api += 1
    taskoutput.append("worker_id: " + field(i.get("worker_id")))
    taskoutput.append("worker_type: " + field(i.get("worker_type")))
    taskoutput.append("user: " + field(i.get("user")))
    taskoutput.append("status: " + fmt_status(i))
    taskoutput.append("starttime: " + fmt_ts(i.get("starttime")))
    taskoutput.append("endtime: " + fmt_ts(i.get("endtime")))
    taskoutput.append("")

taskoutput.append(
    f"errors(out): {pbs_errors_out}  warnings(out,api): {pbs_warnings_out_api}  "
    f"errors_crit_age(<{crit_age_hours}h): {errors_crit_age}  "
    f"errors_warn_age({crit_age_hours}–{warn_age_hours}h): {errors_warn_age}"
)
taskoutput.append("")

# --- GRAFANA/Perfdata-Zähler (nur Counts) ---
ok, err, errors_graph_list = fetch_pbs_tasks(fqdn_pbs, "error", api_token_pbs, SINCE_GRAPH["unix"])
if not ok:
    taskoutput.append(err); taskoutput.append(""); processfailure += 1; errors_graph_list = []

ok, err, warns_graph_list = fetch_pbs_tasks(fqdn_pbs, "warning", api_token_pbs, SINCE_GRAPH["unix"])
if not ok:
    taskoutput.append(err); taskoutput.append(""); processfailure += 1; warns_graph_list = []

pbs_errors_graph = len(errors_graph_list)
pbs_warnings_graph = len(warns_graph_list)

# =============================================================================
# Nagios Exit / Ausgabe
# =============================================================================
perfdata = (
    f"errors_out={pbs_errors_out} warnings_out_api={pbs_warnings_out_api} "
    f"errors_crit_age={errors_crit_age} errors_warn_age={errors_warn_age} "
    f"errors_graph={pbs_errors_graph} warnings_graph={pbs_warnings_graph}"
)

if processfailure > 0:
    print(f"SERVICE STATUS: UNKNOWN | {perfdata}")
    for outputline in taskoutput: print(outputline)
    sys.exit(3)

# Priorität: frische Fehler -> CRITICAL
if errors_crit_age > 0:
    print(f"SERVICE STATUS: CRITICAL | {perfdata}")
    for outputline in taskoutput: print(outputline)
    sys.exit(2)

# Danach mittelalte Fehler oder API-Warnings -> WARNING
if errors_warn_age > 0 or pbs_warnings_out_api > 0:
    print(f"SERVICE STATUS: WARNING | {perfdata}")
    for outputline in taskoutput: print(outputline)
    sys.exit(1)

# Sonst OK
print(f"SERVICE STATUS: OK | {perfdata}")
for outputline in taskoutput: print(outputline)
sys.exit(0)
