#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# 30-compose — 42i-hc Drop-in: iteriert die podman-compose-Dienste unter /srv/*.
# hc-tags: services, local
# hc-ttl: 30
#
# Liefert {"services": [...]}. Pro /srv/<svc> mit compose.yml:
#   - Container-Status via `podman compose ps` (alle up/healthy?),
#   - optional service-eigener Detailcheck /srv/<svc>/health (Exit + stdout),
#   - Metadaten aus /srv/<svc>/service.yaml (name, summary, zones, source, ...).
#
# Geliefert von 42i-oci (dort liegt das podman-Wissen). Wird vom hc-Daemon
# (Paket 42i-hc) genutzt, falls dieser installiert ist. Schema: 42i/intern#236.

import json
import os
import subprocess
import sys

SRV = os.environ.get("HC_SRV_DIR") or "/srv"
TIMEOUT = 8

try:
    import yaml
except Exception:
    yaml = None


def load_meta(svc_dir):
    """Liest /srv/<svc>/service.yaml (falls vorhanden) als Metadaten-Dict."""
    path = os.path.join(svc_dir, "service.yaml")
    if yaml and os.path.isfile(path):
        try:
            with open(path, encoding="utf-8") as fh:
                return yaml.safe_load(fh) or {}
        except Exception:
            return {}
    return {}


def compose_ps(svc_dir):
    """Container-Status eines Stacks; (status, detail) anhand `podman compose ps`."""
    for cmd in (["podman", "compose", "ps", "--format", "json"],
                ["podman-compose", "ps", "--format", "json"]):
        try:
            proc = subprocess.run(cmd, cwd=svc_dir, capture_output=True,
                                  text=True, timeout=TIMEOUT)
        except FileNotFoundError:
            continue
        except subprocess.TimeoutExpired:
            return "timeout", {"message": "podman compose ps Timeout"}
        if proc.returncode != 0:
            continue
        try:
            rows = json.loads(proc.stdout or "[]")
        except json.JSONDecodeError:
            rows = []
        if not rows:
            return "fail", {"message": "keine Container laufen"}
        states = [str(r.get("State", r.get("Status", ""))).lower() for r in rows]
        ok = all("up" in s or "running" in s or "healthy" in s for s in states)
        return ("ok" if ok else "fail"), {"containers": len(rows), "states": states}
    return "no-check", {"message": "podman compose nicht verfuegbar"}


def run_health(svc_dir):
    """Fuehrt /srv/<svc>/health aus; liefert (status, detail-or-message) oder None."""
    health = os.path.join(svc_dir, "health")
    if not (os.path.isfile(health) and os.access(health, os.X_OK)):
        return None
    try:
        proc = subprocess.run([health], cwd=svc_dir, capture_output=True,
                              text=True, timeout=TIMEOUT)
    except subprocess.TimeoutExpired:
        return "timeout", {"message": "health-Timeout"}
    out = (proc.stdout or "").strip()
    status = "ok" if proc.returncode == 0 else "fail"
    if not out:
        return status, {}
    try:
        return status, {"detail": json.loads(out)}
    except json.JSONDecodeError:
        return status, {"message": out[:500]}


def service_enabled(name):
    """Prueft, ob der Stack eine aktivierte systemd-Unit hat (`oci enable`)."""
    try:
        r = subprocess.run(["systemctl", "is-enabled", name + ".service"],
                          capture_output=True, text=True, timeout=3)
        return r.stdout.strip() == "enabled"
    except Exception:
        return False


def main():
    """Scannt /srv/*-Stacks und gibt {"services": [...]} als JSON aus."""
    services = []
    if os.path.isdir(SRV):
        for name in sorted(os.listdir(SRV)):
            svc_dir = os.path.join(SRV, name)
            if not os.path.isfile(os.path.join(svc_dir, "compose.yml")):
                continue
            entry = {"name": name}
            entry.update(load_meta(svc_dir))
            status, detail = compose_ps(svc_dir)
            if status == "fail" and not service_enabled(name):
                # Nicht aktivierter Stack (z. B. oci-Beispiel /srv/sample):
                # bewusst inaktiv, kein Ausfall -> faerbt den Host nicht rot.
                status, detail = "inactive", {"message": "Stack nicht aktiviert"}
            entry["status"] = status
            entry.update(detail)
            hc = run_health(svc_dir)
            if hc:
                hstatus, hdetail = hc
                # health hat Vorrang: Status ersetzen, compose-message verwerfen.
                entry["status"] = hstatus
                entry.pop("message", None)
                entry.update(hdetail)
            services.append(entry)
    json.dump({"services": services}, sys.stdout, ensure_ascii=False)
    sys.stdout.write("\n")


if __name__ == "__main__":
    main()
