#!/usr/bin/env python3
# =========================================================
#   M.IT — Agent de monitoring
#   Version 1.0
#
#   Installation :
#     pip install psutil requests
#
#   Configuration :
#     Modifiez les variables dans la section CONFIG ci-dessous
#
#   Lancement automatique :
#     Windows : Planificateur de tâches (toutes les 5 min)
#     Linux   : crontab -e → */5 * * * * /usr/bin/python3 /opt/mit-agent/agent.py
# =========================================================

import os
import sys
import json
import time
import socket
import platform
import logging
import traceback

try:
    import psutil
except ImportError:
    print("ERREUR : psutil manquant. Installez-le avec : pip install psutil")
    sys.exit(1)

try:
    import requests
except ImportError:
    print("ERREUR : requests manquant. Installez-le avec : pip install requests")
    sys.exit(1)

# =========================================================
#   CONFIG — Modifiez ces valeurs avant de déployer
# =========================================================
API_URL    = "https://m-it.re/api/monitoring.php?push=1"  # API toujours sur m-it.re
API_KEY    = "VOTRE_CLE_API_ICI"          # Clé générée depuis l'admin M.IT
INTERVAL   = 300                           # Secondes entre chaque envoi (5 min)
LOG_FILE   = "mit-agent.log"              # Laisser vide pour désactiver les logs
DEVICE_TYPE = "windows"                   # windows / linux / macos / server / nas
# =========================================================

logging.basicConfig(
    filename=LOG_FILE if LOG_FILE else None,
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)
log = logging.getLogger("mit-agent")


def get_hostname():
    return socket.gethostname()


def get_ip():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        ip = s.getsockname()[0]
        s.close()
        return ip
    except Exception:
        return None


def get_os_version():
    try:
        system = platform.system()
        if system == "Windows":
            return f"Windows {platform.release()} {platform.version()}"
        elif system == "Linux":
            try:
                import distro
                return f"{distro.name()} {distro.version()}"
            except ImportError:
                return f"Linux {platform.release()}"
        elif system == "Darwin":
            return f"macOS {platform.mac_ver()[0]}"
        return platform.platform()
    except Exception:
        return "Inconnu"


def get_cpu_temp():
    """Récupère la température CPU si disponible"""
    try:
        temps = psutil.sensors_temperatures()
        if not temps:
            return None
        # Cherche dans les capteurs courants
        for sensor_name in ['coretemp', 'cpu_thermal', 'k10temp', 'acpitz']:
            if sensor_name in temps:
                entries = temps[sensor_name]
                if entries:
                    return round(entries[0].current, 1)
        # Prend le premier disponible
        for entries in temps.values():
            if entries:
                return round(entries[0].current, 1)
    except (AttributeError, Exception):
        pass
    return None


def get_network_stats():
    """Retourne les octets envoyés/reçus en MB depuis le boot"""
    try:
        net = psutil.net_io_counters()
        return {
            "sent_mb": round(net.bytes_sent / (1024 * 1024), 2),
            "recv_mb": round(net.bytes_recv / (1024 * 1024), 2),
        }
    except Exception:
        return {"sent_mb": None, "recv_mb": None}


def get_disk_usage():
    """Retourne l'usage du disque principal (C: sur Windows, / sur Linux)"""
    try:
        path = "C:\\" if platform.system() == "Windows" else "/"
        disk = psutil.disk_usage(path)
        return {
            "percent":  round(disk.percent, 1),
            "used_gb":  round(disk.used / (1024 ** 3), 2),
            "total_gb": round(disk.total / (1024 ** 3), 2),
        }
    except Exception:
        return {"percent": None, "used_gb": None, "total_gb": None}


def get_windows_services():
    """Vérifie l'état des services Windows critiques"""
    if platform.system() != "Windows":
        return []
    services_to_check = [
        "wuauserv",      # Windows Update
        "MpsSvc",        # Pare-feu Windows
        "WinDefend",     # Windows Defender
    ]
    results = []
    try:
        for svc_name in services_to_check:
            try:
                svc = psutil.win_service_get(svc_name)
                results.append({
                    "name":   svc_name,
                    "status": svc.as_dict().get("status", "unknown"),
                })
            except Exception:
                results.append({"name": svc_name, "status": "not_found"})
    except Exception:
        pass
    return results


def collect_metrics():
    """Collecte toutes les métriques de la machine"""

    # CPU (moyenne sur 1 seconde)
    cpu_percent = psutil.cpu_percent(interval=1)

    # RAM
    ram = psutil.virtual_memory()

    # Disque
    disk = get_disk_usage()

    # Réseau
    net = get_network_stats()

    # Température
    cpu_temp = get_cpu_temp()

    # Uptime
    uptime = int(time.time() - psutil.boot_time())

    # Processus
    proc_count = len(psutil.pids())

    metrics = {
        "cpu_percent":     round(cpu_percent, 1),
        "ram_percent":     round(ram.percent, 1),
        "ram_used_gb":     round(ram.used / (1024 ** 3), 2),
        "ram_total_gb":    round(ram.total / (1024 ** 3), 2),
        "disk_percent":    disk["percent"],
        "disk_used_gb":    disk["used_gb"],
        "disk_total_gb":   disk["total_gb"],
        "cpu_temp":        cpu_temp,
        "uptime_seconds":  uptime,
        "network_sent_mb": net["sent_mb"],
        "network_recv_mb": net["recv_mb"],
        "processes_count": proc_count,
    }

    # Infos supplémentaires
    extra = {
        "cpu_cores":       psutil.cpu_count(logical=True),
        "cpu_freq_mhz":    round(psutil.cpu_freq().current, 0) if psutil.cpu_freq() else None,
        "boot_time":       time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(psutil.boot_time())),
        "services":        get_windows_services(),
    }

    return metrics, extra


def send_data(metrics, extra):
    """Envoie les données à l'API M.IT"""
    payload = {
        "hostname":      get_hostname(),
        "device_type":   DEVICE_TYPE,
        "ip_address":    get_ip(),
        "os_version":    get_os_version(),
        "agent_version": "1.0",
        "metrics":       metrics,
        "extra":         extra,
    }

    headers = {
        "Content-Type":  "application/json",
        "X-MIT-API-Key": API_KEY,
    }

    response = requests.post(
        API_URL,
        json=payload,
        headers=headers,
        timeout=30,
        verify=True,
    )

    if response.status_code == 200:
        data = response.json()
        if data.get("success"):
            log.info(f"Données envoyées — Device ID: {data.get('device_id')}")
            return True
        else:
            log.error(f"Erreur API : {data.get('message')}")
    else:
        log.error(f"Erreur HTTP {response.status_code}: {response.text[:200]}")

    return False


def run_once():
    """Exécute une collecte et un envoi unique"""
    log.info(f"Collecte des métriques sur {get_hostname()}...")
    try:
        metrics, extra = collect_metrics()
        success = send_data(metrics, extra)
        if success:
            print(f"[OK] Données envoyées — CPU: {metrics['cpu_percent']}% | RAM: {metrics['ram_percent']}% | Disque: {metrics['disk_percent']}%")
        else:
            print("[ERREUR] Envoi échoué — vérifiez les logs")
    except Exception as e:
        log.error(f"Erreur inattendue : {traceback.format_exc()}")
        print(f"[ERREUR] {e}")


def run_loop():
    """Boucle infinie — pour lancer en service"""
    log.info(f"Agent M.IT démarré — intervalle: {INTERVAL}s")
    print(f"Agent M.IT démarré. Envoi toutes les {INTERVAL} secondes. Ctrl+C pour arrêter.")
    while True:
        run_once()
        time.sleep(INTERVAL)


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "--loop":
        run_loop()
    else:
        # Mode par défaut : une seule exécution (pour cron/planificateur)
        run_once()
