#!/usr/bin/env python3
"""jarvis-system-info — Informations système complètes de Jarvis."""

import os
import re
import socket
import subprocess
import sys


def run(args: list[str]) -> tuple[str, int]:
    """Exécute une commande et retourne (stdout.strip(), returncode)."""
    try:
        result = subprocess.run(
            args, capture_output=True, text=True, timeout=10)
        return result.stdout.strip(), result.returncode
    except Exception as exc:
        return f"ERROR: {exc}", -1


def format_bytes(nbytes: int) -> str:
    """Formate une taille en octets avec la plus grande unité adaptée."""
    for unit in ("", "Ki", "Mi", "Gi"):
        if abs(nbytes) < 1024 or unit == "Gi":
            return f"{nbytes:.1f}{unit}B"
        nbytes /= 1024
    return f"{nbytes:.1f}TiB"


# ── collectors ─────────────────────────────────────────────────────

def _get_hostname() -> str:
    out, _ = run(["hostname"])
    return out or "N/A"


def _get_ubuntu_version() -> str:
    """Parse /etc/os-release pour obtenir PRETTY_NAME."""
    try:
        with open("/etc/os-release") as f:
            for line in f:
                line = line.strip()
                if line.startswith("PRETTY_NAME="):
                    name = line.split("=", 1)[1]
                    return name.strip('"')
    except Exception:
        pass
    return "N/A"


def _get_kernel() -> str:
    out, _ = run(["uname", "-r"])
    return out or "N/A"


def _get_logical_cpus() -> int:
    """Retourne le nombre de CPU logiques."""
    count = os.cpu_count() or 0
    if count > 0:
        return count
    out, rc = run(["nproc"])
    if rc == 0 and out.isdigit():
        return int(out)
    return -1


def _get_memory() -> tuple[str, str]:
    """Retourne (mémoire totale, mémoire disponible en format humain)."""
    # Méthode 1 : free -h donne des valeurs lisibles directement
    out, rc = run(["free", "-h"])
    if rc == 0 and out:
        for line in out.strip().split("\n"):
            parts = line.split()
            if len(parts) >= 2 and parts[0].lower() == "mem":
                total = parts[1]
                available = parts[-1]  # dernière colonne (Available sur free >= 8.14)
                return total, available

    # Méthode 2 : /proc/meminfo en milliers d'octets (kB)
    try:
        with open("/proc/meminfo") as f:
            info: dict[str, int] = {}
            for line in f:
                m = re.match(r"^\s*(\w[\w\s]*?)\s*:\s*(\d+)\s*kB", line)
                if m:
                    info[m.group(1).strip()] = int(m.group(2))  # type: ignore[operator]

        total_kb = info.get("MemTotal", 0) or 0
        avail_kb = info.get("MemAvailable", 0) or 0

        def _to_human(kb: int) -> str:
            if kb >= 1_048_576:
                return f"{kb / 1_048_576:.1f} Gi"
            elif kb >= 1024:
                return f"{kb / 1024:.1f} Mi"
            return f"{kb} Ki"

        return (_to_human(total_kb), _to_human(avail_kb))
    except Exception:
        pass

    return "N/A", "N/A"


def _get_disk_root() -> str:
    """Espace disque disponible sur / pour l'utilisateur standard."""
    try:
        st = os.statvfs("/")
        avail_bytes = st.f_bavail * st.f_frsize
        return format_bytes(avail_bytes)
    except Exception:
        pass

    # Fallback : df -h
    out, rc = run(["df", "-h", "/"])
    if rc == 0 and out:
        parts = out.strip().split("\n")
        if len(parts) >= 2:
            cols = parts[1].split()
            if len(cols) >= 4:
                return cols[3]
    return "N/A"


def _get_ipv4_main() -> str:
    """Adresse IPv4 principale de la machine (interface physique, non loopback)."""
    hostname = _get_hostname()

    # Méthode 1 : via socket.getaddrinfo
    try:
        infos = socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM)
        ips = [ip[4][0] for ip in infos if not ip[4][0].startswith("127.")]  # type: ignore[index]
        if ips:
            return ips[0]
    except Exception:
        pass

    # Méthode 2 : parser la sortie de « ip -4 addr »
    out, rc = run(["ip", "-4", "addr"])
    if rc == 0 and out:
        for line in out.strip().split("\n"):
            m = re.search(r"inet (\d+\.\d+\.\d+\.\d+)", line)
            if m:
                ip_addr = m.group(1)
                if not ip_addr.startswith("127.") and not ip_addr.startswith("0."):
                    return ip_addr

    # Méthode 3 : gethostbyname en dernier recours
    try:
        return socket.gethostbyname(hostname)
    except Exception:
        pass

    return "N/A"


def _get_default_route() -> str:
    """Adresse IP de la passerelle par défaut."""
    out, rc = run(["ip", "-4", "route"])
    if rc == 0 and out:
        for line in out.strip().split("\n"):
            if line.startswith("default"):
                m = re.search(r"via\s+(\d+\.\d+\.\d+\.\d+)", line)
                if m:
                    return m.group(1)
    return "N/A"


def _get_ollama_status() -> str:
    """État du service systemd ollama."""
    out, _ = run(["systemctl", "is-active", "ollama"])
    return out or "unknown"


def _get_ollama_models() -> list[dict[str, str]]:
    """Modèles chargés avec « ollama ps » — retourne une liste de dictionnaires."""
    out, rc = run(["ollama", "ps"])
    if rc != 0 or not out:
        return []

    lines = out.strip().split("\n")
    models: list[dict[str, str]] = []
    for line in lines[1:]:       # skip la ligne d'entête
        if not line.strip():
            continue
        parts = line.split()
        if len(parts) < 6:     # minimum : name id size(1+) proc(1+) context until+
            continue

        name = parts[0]
        rid = parts[1]
        
        # Find the PROCESSOR column — first token containing '%'
        proc_start = None
        for i in range(2, len(parts)):
            if "%" in parts[i]:
                proc_start = i
                break
        
        if proc_start is None or proc_start < 3:  # need room for SIZE at least
            continue

        size_parts = parts[2:proc_start]
        size_str = " ".join(size_parts).strip()

        # Processor continues: "%" token + optional suffix (GPU, CPU, …)
        proc_tokens = [parts[proc_start]]  # e.g. "100%"
        j = proc_start + 1
        while j < len(parts):
            nxt = parts[j]
            # A new column starts when we hit a pure-integer or something that looks like context/timestamp
            if nxt.isdigit() and proc_start is not None:
                break
            if re.match(r"^\d+$", nxt):  # pure number → likely CONTEXT col
                break
            if re.match(r"^\d{1,2}\s+(minutes?|hour|second|day)", nxt):  # timestamp-like
                break
            proc_tokens.append(nxt)
            j += 1
        processor_str = " ".join(proc_tokens).strip()

        # Next token(s) = CONTEXT (usually a single integer)
        if j < len(parts):
            context = parts[j]
            j += 1
        else:
            context = "N/A"

        # Everything after = UNTIL (may be multi-word)
        until_str = " ".join(parts[j:]).strip() if j < len(parts) else "N/A"

        model_info: dict[str, str] = {
            "name":      name,
            "id_short":  "..." + rid[-6:],
            "size":      size_str,
            "processor": processor_str,
            "context":   context,
            "until":     until_str,
        }
        models.append(model_info)

    return models


def _get_hermes_gateway_status() -> str:
    """État du service utilisateur hermes-gateway.service."""
    out, _ = run(["systemctl", "--user", "is-active", "hermes-gateway.service"])
    return out or "unknown"


# ── affichage ───────────────────────────────────────────────────────

def main() -> int:
    separator = "=" * 54
    print(f"\n{separator}")
    print("  JARVIS SYSTEM INFORMATION")
    print(f"{separator}\n")

    # 1) Identités
    hostname       = _get_hostname()
    ubuntu_version = _get_ubuntu_version()
    kernel         = _get_kernel()
    logical_cpus   = _get_logical_cpus()

    print("[SYSTEME]")
    print(f"  Hostname      : {hostname}")
    print(f"  Ubuntu        : {ubuntu_version}")
    print(f"  Kernel        : {kernel}")
    print(f"  CPUs (logique): {logical_cpus}\n")

    # 2) Mémoire & disque
    mem_total, mem_avail = _get_memory()
    disk_root            = _get_disk_root()

    print("[RESSOURCES]")
    print(f"  Mémoire total : {mem_total}")
    print(f"  Mémoire dispo : {mem_avail}")
    print(f"  Disque /      : {disk_root}\n")

    # 3) Réseau
    ipv4_main = _get_ipv4_main()
    default_route = _get_default_route()

    print("[RESEAU]")
    print(f"  IPv4 principal : {ipv4_main}")
    print(f"  Route par defaut: {default_route}\n")

    # 4) Ollama
    ollama_status = _get_ollama_status()
    ollama_models = _get_ollama_models()

    if ollama_status == "active":
        ollama_tag = "[OK]"
    else:
        ollama_tag = "[WARN]"

    print("[SERVICES — OLLAMA]")
    print(f"  Status : {ollama_status} ({ollama_tag})")
    if ollama_models:
        for mod in ollama_models:
            print(f'  {mod.get("name", "N/A")}  |  '
                  f'{mod.get("size", "N/A")}  |  '
                  f'{mod.get("processor", "N/A")}  |  '
                  f'{mod.get("context", "N/A")}'
                  )
    else:
        print("  Aucun modele charge\n")

    # 5) Hermes Gateway
    hermes_status = _get_hermes_gateway_status()
    if hermes_status == "active":
        hermes_tag = "[OK]"
    else:
        hermes_tag = "[WARN]"

    print("[SERVICES — HERMES GATEWAY]")
    print(f"  Status : {hermes_status} ({hermes_tag})\n")

    print(separator)
    print()

    return 0


if __name__ == "__main__":
    sys.exit(main())
