#!/usr/bin/env python3
"""LeMaker local health observations v1.0.0; Python 3.9+, Linux, standard library.

Read this file before running: python3 lemaker-health-check-v1.py
Prints bounded JSON to stdout. No files are written and no network is used.
An optional shell redirection chosen by the user can save that visible output.
This collector neither installs nor starts the FleetCare agent.
SPDX-License-Identifier: MIT
"""

import json
import os
import re
import subprocess
import sys
from pathlib import Path

VERSION = "1.0.0"


def read_small(filename, limit=65536):
    try:
        with open(filename, "r", encoding="utf-8", errors="replace") as stream:
            value = stream.read(limit + 1)
        return value if len(value) <= limit else None
    except (OSError, ValueError):
        return None


def token(value, limit=80):
    return (
        value
        if isinstance(value, str)
        and len(value) <= limit
        and re.fullmatch(r"[A-Za-z0-9_.+~-]+", value)
        else None
    )


def failed_count():
    try:
        # Fixed arguments, fixed scalar property. No unit names are requested.
        result = subprocess.run(
            ["systemctl", "show", "--property=NFailedUnits", "--value", "--no-pager"],
            capture_output=True,
            text=True,
            timeout=2,
            check=False,
        )
        value = result.stdout.strip()
        if result.returncode == 0 and len(value) <= 8 and value.isdecimal():
            count = int(value)
            return count if count <= 100000 else None
    except (OSError, subprocess.TimeoutExpired, ValueError):
        pass
    return None


def collect():
    report = {
        "schema_version": 1,
        "collector_version": VERSION,
        "context": "unknown",
        "architecture": None,
        "os_id": None,
        "os_version": None,
        "kernel_release": None,
        "uptime_seconds": None,
        "memory_total_bytes": None,
        "memory_available_bytes": None,
        "root_total_bytes": None,
        "root_available_bytes": None,
        "root_read_only": None,
        "temperatures": [],
        "failed_service_count": None,
    }
    if not sys.platform.startswith("linux"):
        return report
    values = {}
    for line in (read_small("/etc/os-release", 16384) or "").splitlines():
        key, separator, value = line.partition("=")
        if separator and key in ("ID", "VERSION_ID"):
            values[key] = token(value.strip("\"'"))
    report["os_id"], report["os_version"] = values.get("ID"), values.get("VERSION_ID")
    report["context"] = (
        "openwrt"
        if values.get("ID") == "openwrt"
        else "haos"
        if values.get("ID") in ("hassos", "haos")
        else "container"
        if Path("/.dockerenv").exists() or Path("/run/.containerenv").exists()
        else "linux_host_unverified"
    )
    report["architecture"] = token(os.uname().machine)
    report["kernel_release"] = token(os.uname().release)
    uptime = read_small("/proc/uptime", 256)
    try:
        report["uptime_seconds"] = int(float(uptime.split()[0])) if uptime else None
    except (ValueError, IndexError, OverflowError):
        pass
    memory = read_small("/proc/meminfo", 16384)
    if memory:
        for field, name in [
            ("MemTotal", "memory_total_bytes"),
            ("MemAvailable", "memory_available_bytes"),
        ]:
            match = re.search(r"^" + field + r":\s+(\d+) kB$", memory, re.MULTILINE)
            if match:
                report[name] = int(match[1]) * 1024
    try:
        disk = os.statvfs("/")
        report["root_total_bytes"] = disk.f_blocks * disk.f_frsize
        report["root_available_bytes"] = disk.f_bavail * disk.f_frsize
        report["root_read_only"] = bool(disk.f_flag & os.ST_RDONLY)
    except (AttributeError, OSError):
        pass
    # Numbered aliases deliberately exclude raw sensor paths, serials and labels.
    sensors = sorted(Path("/sys/class/thermal").glob("thermal_zone[0-9]*/temp"))[:8]
    for index, filename in enumerate(sensors):
        value = read_small(filename, 32)
        try:
            temp = int(value.strip()) / 1000 if value else None
            if temp is not None and -50 <= temp <= 200:
                report["temperatures"].append(
                    {"sensor": "thermal-" + str(index + 1), "celsius": temp}
                )
        except ValueError:
            pass
    report["failed_service_count"] = failed_count()
    return report


if __name__ == "__main__":
    print(json.dumps(collect(), indent=2, allow_nan=False))
