import threading
import time
import random
import uuid
import os
import json
import paramiko
import re
from flask import Flask, jsonify, render_template, request
from network_scanner import scan_network, get_local_ip
from router_controller import RouterController
from server_controller import ServerController

app = Flask(__name__)

SERVERS_JSON_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "servers.json")

def load_servers():
    if os.path.exists(SERVERS_JSON_PATH):
        try:
            with open(SERVERS_JSON_PATH, "r") as f:
                servers = json.load(f)
                # Auto-detect OS type for local host cross-platform compatibility
                for s in servers:
                    if s.get("id") == "local-host" or s.get("type") == "local":
                        s["os_type"] = "linux" if os.name != 'nt' else "windows"
                return servers
        except Exception as e:
            print(f"Error loading servers.json: {e}")
    default_servers = [
        {
            "id": "simulated-1",
            "name": "Server-Simulasi-Linux",
            "type": "simulated",
            "ip": "192.168.1.10",
            "username": "root",
            "password": "",
            "os_type": "linux"
        },
        {
            "id": "local-host",
            "name": "Komputer-Host-Lokal",
            "type": "local",
            "ip": "127.0.0.1",
            "username": "",
            "password": "",
            "os_type": "windows"
        }
    ]
    save_servers(default_servers)
    return default_servers

def save_servers(servers):
    try:
        with open(SERVERS_JSON_PATH, "w") as f:
            json.dump(servers, f, indent=4)
    except Exception as e:
        print(f"Error saving servers.json: {e}")

ROUTERS_JSON_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "routers.json")

def load_routers():
    if os.path.exists(ROUTERS_JSON_PATH):
        try:
            with open(ROUTERS_JSON_PATH, "r") as f:
                return json.load(f)
        except Exception as e:
            print(f"Error loading routers.json: {e}")
    return [
        {
            "id": "router-simulated",
            "name": "Simulated Router",
            "type": "simulated",
            "ip": "192.168.88.1",
            "username": "admin",
            "password": ""
        }
    ]

def save_routers(routers):
    try:
        with open(ROUTERS_JSON_PATH, "w") as f:
            json.dump(routers, f, indent=4)
    except Exception as e:
        print(f"Error saving routers.json: {e}")

# Configuration WhatsApp Notifier Helpers
WHATSAPP_CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "whatsapp_config.json")

def load_whatsapp_config():
    if os.path.exists(WHATSAPP_CONFIG_PATH):
        try:
            with open(WHATSAPP_CONFIG_PATH, "r") as f:
                return json.load(f)
        except Exception as e:
            print(f"Error loading whatsapp_config.json: {e}")
    return {
        "enabled": False,
        "phone_number": "",
        "gateway_type": "fonnte",
        "api_token": "",
        "instance_id": ""
    }

def save_whatsapp_config(config):
    try:
        with open(WHATSAPP_CONFIG_PATH, "w") as f:
            json.dump(config, f, indent=4)
    except Exception as e:
        print(f"Error saving whatsapp_config.json: {e}")

# State Global Jaringan & Router
app_state = {
    "simulation_active": True,
    "scanning": False,
    "last_scan_time": None,
    "real_devices": [],
    "blocked_macs": set(),
    "limited_macs": {},  # MAC -> speed limit in Mbps
    "custom_names": {},   # MAC -> custom hostname
    
    # Integrasi Router List
    "routers": load_routers(),
    "active_router_id": load_routers()[0]["id"] if load_routers() else "router-simulated",
    
    # Daftar Server (Multi-Server)
    "servers": load_servers(),
    "whatsapp": load_whatsapp_config(),
    
    # Toggles Simulasi Ancaman Jaringan
    "simulations": {
        "duplicate_ip": True,
        "high_bandwidth": True,
        "offline_device": True,
        "virus_infected": True,
        "hacker_intruder": True
    }
}

# Factory Cache Router Controllers
router_controllers = {}

def get_router_ctrl(router_id=None):
    if not router_id:
        router_id = app_state.get("active_router_id")
    if not router_id:
        return RouterController("simulated")
        
    router_cfg = next((r for r in app_state["routers"] if r["id"] == router_id), None)
    if not router_cfg:
        return RouterController("simulated")
        
    if router_id not in router_controllers:
        router_controllers[router_id] = RouterController(
            router_cfg["type"], router_cfg["ip"], router_cfg["username"], router_cfg["password"]
        )
    else:
        # Sinkronkan kredensial terbaru jika di-update
        ctrl = router_controllers[router_id]
        ctrl.router_type = router_cfg["type"]
        
        # Ekstrak port kustom jika ada
        ip = router_cfg["ip"]
        if ":" in ip:
            parts = ip.split(":")
            ctrl.ip = parts[0]
            try:
                ctrl.port = int(parts[1])
            except ValueError:
                pass
        else:
            ctrl.ip = ip
            ctrl.port = 22
            
        ctrl.username = router_cfg["username"]
        ctrl.password = router_cfg["password"]
        
    return router_controllers[router_id]

# Hubungkan router default secara asinkron di awal
def init_routers_connection():
    for r in app_state["routers"]:
        if r["type"] != "simulated":
            try:
                ctrl = get_router_ctrl(r["id"])
                threading.Thread(target=ctrl.test_connection, daemon=True).start()
            except Exception:
                pass

init_routers_connection()

# Inisialisasi Kontroler Server Global (Dikelola dalam dict ID -> Controller)
server_controllers = {}
for s in app_state["servers"]:
    server_controllers[s["id"]] = ServerController(s["type"], s["ip"], s["username"], s["password"], s["os_type"])

cached_server_metrics = {}

def poll_server_metrics_worker(s):
    try:
        srv_id = s["id"]
        ctrl = server_controllers.get(srv_id)
        if not ctrl:
            ctrl = ServerController(s["type"], s["ip"], s["username"], s["password"], s["os_type"])
            server_controllers[srv_id] = ctrl
        metrics = ctrl.get_server_status()
        cached_server_metrics[srv_id] = metrics
    except Exception as e:
        print(f"Error in polling worker for server {s.get('id')}: {e}")

def update_servers_metrics_loop():
    while True:
        try:
            servers = list(app_state["servers"])
            threads = []
            for s in servers:
                t = threading.Thread(target=poll_server_metrics_worker, args=(s,), daemon=True)
                t.start()
                threads.append(t)
            
            # Tunggu semua thread selesai atau timeout maksimal 5 detik
            for t in threads:
                t.join(timeout=5.0)
        except Exception as e:
            print(f"Error in metrics loop: {e}")
        time.sleep(3.0)

# Mulai loop background untuk pemantauan server
metrics_thread = threading.Thread(target=update_servers_metrics_loop, daemon=True)
metrics_thread.start()

# Perangkat simulasi standar untuk mengisi data
MOCK_DEVICES = [
    {"ip": "192.168.1.1", "mac": "00:14:22:a1:b2:c3", "vendor": "Cisco Systems", "hostname": "Gateway-Router", "type": "dynamic", "is_local": False},
    {"ip": "192.168.1.10", "mac": "00:0c:29:ab:cd:ef", "vendor": "VMware", "hostname": "Server-File-Lokal", "type": "dynamic", "is_local": False},
    {"ip": "192.168.1.33", "mac": "e0:db:55:12:34:56", "vendor": "TP-Link", "hostname": "User-PC", "type": "dynamic", "is_local": False},
    {"ip": "192.168.1.100", "mac": "70:8b:cd:aa:bb:cc", "vendor": "ASUSTek Computer", "hostname": "Workstation-Render", "type": "dynamic", "is_local": False},
    {"ip": "192.168.1.150", "mac": "fc:fb:fb:11:22:33", "vendor": "Apple", "hostname": "iPhone-Sesar", "type": "dynamic", "is_local": False},
    {"ip": "192.168.1.180", "mac": "04:d6:aa:88:99:00", "vendor": "Xiaomi", "hostname": "SmartTV-LivingRoom", "type": "dynamic", "is_local": False}
]

def run_async_scan():
    """Menjalankan scanner jaringan di background agar Flask tidak block."""
    global app_state
    app_state["scanning"] = True
    try:
        router_devices = []
        for r in app_state.get("routers", []):
            if r.get("type") != "simulated":
                try:
                    ctrl = get_router_ctrl(r["id"])
                    # Selalu tes koneksi dulu jika terputus
                    if not ctrl.is_connected:
                        ctrl.test_connection()
                    if ctrl.is_connected:
                        router_devices.extend(ctrl.get_devices())
                except Exception as e:
                    print(f"Error getting devices from router {r.get('name')}: {e}")
                
        local_devices = scan_network()
        
        # Merge lists by IP address to combine local scan and Mikrotik DHCP leases
        merged = {}
        for d in local_devices:
            ip = d["ip"]
            merged[ip] = d
            
        for d in router_devices:
            ip = d["ip"]
            if ip in merged:
                # If local resolved name is a generic template, prefer router hostname if available
                if merged[ip]["hostname"].startswith("Perangkat-") or merged[ip]["hostname"].startswith("IP-") or merged[ip]["hostname"] == "Perangkat LAN":
                    if not d["hostname"].startswith("LAN-Device-") and not d["hostname"].startswith("IP-"):
                        merged[ip]["hostname"] = d["hostname"]
                # Save lease info as vendor if vendor is empty or unknown
                if merged[ip]["vendor"] == "Unknown Device/Vendor" or merged[ip]["vendor"] == "Unknown":
                    merged[ip]["vendor"] = d["vendor"]
            else:
                merged[ip] = d
                
        # Resolve hostnames for all merged devices that still have generic templates IN PARALLEL!
        from network_scanner import get_hostname, load_ssh_credentials
        from concurrent.futures import ThreadPoolExecutor
        ssh_creds = load_ssh_credentials()
        
        devices_to_resolve = []
        for ip, d in merged.items():
            if d.get("is_local"):
                continue
            hn = d.get("hostname", "")
            if (hn.startswith("IP-") or 
                hn.startswith("LAN-Device-") or 
                hn.startswith("Perangkat-") or 
                hn == "Perangkat LAN" or 
                not hn):
                devices_to_resolve.append(d)
                
        def resolve_worker(d):
            try:
                resolved = get_hostname(d["ip"], d["vendor"], ssh_creds)
                if resolved:
                    d["hostname"] = resolved
            except Exception as e:
                print(f"Error resolving {d['ip']}: {e}")
                
        if devices_to_resolve:
            with ThreadPoolExecutor(max_workers=15) as executor:
                # Map resolution tasks and let ThreadPoolExecutor execute them in parallel
                executor.map(resolve_worker, devices_to_resolve)
                
        app_state["real_devices"] = list(merged.values())
        app_state["last_scan_time"] = time.strftime("%H:%M:%S")
    except Exception as e:
        print(f"Error during scan: {e}")
    finally:
        app_state["scanning"] = False

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/api/status")
def get_status():
    return jsonify({
        "simulation_active": app_state["simulation_active"],
        "scanning": app_state["scanning"],
        "last_scan_time": app_state["last_scan_time"] or "Belum pernah",
        "simulations_toggles": app_state["simulations"]
    })

def get_processed_devices_list():
    devices_to_return = []
    
    if app_state["real_devices"]:
        devices_to_return = [d.copy() for d in app_state["real_devices"]]
    else:
        local_ip = get_local_ip()
        ip_parts = local_ip.split(".")
        gateway_ip = ".".join(ip_parts[:3]) + ".1" if len(ip_parts) == 4 else "192.168.1.1"
        
        import socket
        devices_to_return = [
            {"ip": local_ip, "mac": "N/A (Host Lokal)", "vendor": "PC Host Anda", "hostname": socket.gethostname(), "type": "Local Host", "is_local": True},
            {"ip": gateway_ip, "mac": "04:f4:1c:71:3b:6d", "vendor": "Router/Gateway", "hostname": "Router-Gateway", "type": "dynamic", "is_local": False}
        ]
        
    local_ip = get_local_ip()
    ip_parts = local_ip.split(".")
    subnet_prefix = ".".join(ip_parts[:3]) if len(ip_parts) == 4 else "192.168.1"

    if app_state["simulation_active"]:
        for m_dev in MOCK_DEVICES:
            mock_ip_parts = m_dev["ip"].split(".")
            m_ip = f"{subnet_prefix}.{mock_ip_parts[-1]}"
            if not any(d["ip"] == m_ip for d in devices_to_return):
                c_dev = m_dev.copy()
                c_dev["ip"] = m_ip
                devices_to_return.append(c_dev)

    processed_devices = []
    for dev in devices_to_return:
        dev = dev.copy()
        mac = dev["mac"]
        ip = dev["ip"]
        
        if ip.startswith("192.168.1.") and subnet_prefix != "192.168.1":
            ip_suffix = ip.split(".")[-1]
            ip = f"{subnet_prefix}.{ip_suffix}"
            dev["ip"] = ip
        
        if mac in app_state["custom_names"]:
            dev["hostname"] = app_state["custom_names"][mac]
            
        down_speed = round(random.uniform(0.1, 1.5), 1)
        up_speed = round(random.uniform(0.05, 0.5), 1)
        dev["bandwidth_down"] = down_speed
        dev["bandwidth_up"] = up_speed
        
        dev["status"] = "Online"
        dev["threat_level"] = "safe"
        dev["threat_desc"] = "Aman"
        
        if mac in app_state["blocked_macs"]:
            dev["status"] = "Blocked"
            dev["threat_level"] = "warning"
            dev["threat_desc"] = "Akses Jaringan Diblokir"
            dev["bandwidth_down"] = 0.0
            dev["bandwidth_up"] = 0.0
            
        if mac in app_state["limited_macs"] and dev["status"] != "Blocked":
            limit = app_state["limited_macs"][mac]
            dev["status"] = f"Limited ({limit} Mbps)"
            dev["bandwidth_down"] = min(dev["bandwidth_down"], float(limit))
            dev["bandwidth_up"] = min(dev["bandwidth_up"], float(limit) * 0.5)

        if not app_state["simulation_active"]:
            inventory = load_inventory()
            registered_macs = {item["mac"].lower() for item in inventory}
            
            is_dup = False
            for other in devices_to_return:
                if other["ip"] == ip and other["mac"] != mac and mac != "N/A (Host Lokal)" and other["mac"] != "N/A (Host Lokal)":
                    is_dup = True
                    break
                    
            if is_dup:
                dev["threat_level"] = "critical"
                dev["threat_desc"] = "Bentrokan IP Jaringan (IP Conflict)"
            else:
                is_intruder_host = any(term in dev["hostname"].lower() for term in ["kali", "parrot", "backtrack", "intruder", "metasploit"])
                if is_intruder_host:
                    dev["threat_level"] = "critical"
                    dev["threat_desc"] = "Perangkat Intruder / Kali Linux"
                elif mac.lower() not in registered_macs and mac != "N/A (Host Lokal)" and not mac.startswith("N/A"):
                    dev["threat_level"] = "warning"
                    dev["threat_desc"] = "Perangkat Asing (Belum Terdaftar)"
                elif dev["bandwidth_down"] > 10.0:
                    dev["threat_level"] = "warning"
                    dev["threat_desc"] = "Pemakaian Bandwidth Tinggi"

        if app_state["simulation_active"]:
            if app_state["simulations"]["duplicate_ip"] and ip == f"{subnet_prefix}.33":
                dev["ip"] = f"{subnet_prefix}.150"
                dev["threat_level"] = "critical"
                dev["threat_desc"] = "Konflik IP Duplikat dengan iPhone-Sesar"
                
            if app_state["simulations"]["high_bandwidth"] and ip == f"{subnet_prefix}.100":
                dev["bandwidth_down"] = round(random.uniform(85.0, 99.5), 1)
                dev["bandwidth_up"] = round(random.uniform(15.0, 25.0), 1)
                dev["threat_level"] = "warning"
                dev["threat_desc"] = "Pemakaian Bandwidth Ekstrim (> 50 MB/s)"
                
            if app_state["simulations"]["offline_device"] and ip == f"{subnet_prefix}.10":
                dev["status"] = "Offline"
                dev["threat_level"] = "warning"
                dev["threat_desc"] = "Server Lokal Terputus (Offline)"
                dev["bandwidth_down"] = 0.0
                dev["bandwidth_up"] = 0.0
                
            if app_state["simulations"]["virus_infected"] and ip == f"{subnet_prefix}.33" and not app_state["simulations"]["duplicate_ip"]:
                dev["threat_level"] = "critical"
                dev["threat_desc"] = "Terdeteksi Virus / Botnet aktif (port outbound 4444)"
                
        processed_devices.append(dev)

    if app_state["simulation_active"] and app_state["simulations"]["hacker_intruder"]:
        processed_devices.append({
            "ip": f"{subnet_prefix}.200",
            "mac": "00:11:22:99:88:77",
            "vendor": "Unknown (Shenzhen Tech)",
            "hostname": "kali-linux-intruder",
            "type": "dynamic",
            "is_local": False,
            "bandwidth_down": 12.4,
            "bandwidth_up": 8.1,
            "status": "Online" if "00:11:22:99:88:77" not in app_state["blocked_macs"] else "Blocked",
            "threat_level": "critical",
            "threat_desc": "Penyusup Aktif: Port Scanning!"
        })
        
    return processed_devices

@app.route("/api/devices")
def get_devices():
    return jsonify(get_processed_devices_list())

# Cumulative Bandwidth Volumes & History Database
device_bandwidth_volumes = {}
BANDWIDTH_HISTORY_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bandwidth_history.json")

def load_bandwidth_history():
    if os.path.exists(BANDWIDTH_HISTORY_PATH):
        try:
            with open(BANDWIDTH_HISTORY_PATH, "r") as f:
                return json.load(f)
        except Exception as e:
            print(f"Error loading bandwidth_history.json: {e}")
            
    # Mock data generation if file is missing (to make demo rich with historical dates/months/years)
    import datetime
    today = datetime.date.today()
    history = {}
    
    dates_to_gen = [
        today,
        today - datetime.timedelta(days=1),
        today - datetime.timedelta(days=2),
        today - datetime.timedelta(days=3),
        datetime.date(2026, 6, 15),
        datetime.date(2026, 6, 20),
        datetime.date(2025, 12, 10),
        datetime.date(2025, 12, 25)
    ]
    
    macs_and_names = [
        ("N/A (Host Lokal)", "PC Host Anda", "192.168.10.80"),
        ("04:f4:1c:71:3b:6d", "Router-Gateway", "192.168.10.1"),
        ("00:25:90:3c:de:12", "Server-EDP", "192.168.10.10"),
        ("c8:d7:19:bc:45:ef", "iPhone-Sesar", "192.168.10.33"),
        ("f4:0f:24:aa:bb:cc", "Smart-TV-EDP", "192.168.10.100")
    ]
    
    for d in dates_to_gen:
        date_str = d.strftime("%Y-%m-%d")
        history[date_str] = {}
        for mac, name, ip in macs_and_names:
            history[date_str][mac] = {
                "ip": ip,
                "name": name,
                "int_down": round(random.uniform(0.5, 4.0), 2),
                "int_up": round(random.uniform(0.1, 1.2), 2),
                "loc_down": round(random.uniform(2.0, 15.0), 2),
                "loc_up": round(random.uniform(0.5, 8.0), 2)
            }
            
    save_bandwidth_history(history)
    return history

def save_bandwidth_history(history):
    try:
        with open(BANDWIDTH_HISTORY_PATH, "w") as f:
            json.dump(history, f, indent=2)
    except Exception as e:
        print(f"Error saving bandwidth_history.json: {e}")

def update_device_bandwidth_volumes(devices):
    import datetime
    today_str = datetime.date.today().strftime("%Y-%m-%d")
    history = load_bandwidth_history()
    
    for dev in devices:
        mac = dev["mac"]
        if mac not in device_bandwidth_volumes:
            # Restore from history if it exists for today
            if today_str in history and mac in history[today_str]:
                h_vol = history[today_str][mac]
                device_bandwidth_volumes[mac] = {
                    "int_down": h_vol.get("int_down", 0.0),
                    "int_up": h_vol.get("int_up", 0.0),
                    "loc_down": h_vol.get("loc_down", 0.0),
                    "loc_up": h_vol.get("loc_up", 0.0)
                }
            else:
                device_bandwidth_volumes[mac] = {
                    "int_down": round(random.uniform(0.1, 1.5), 4),
                    "int_up": round(random.uniform(0.05, 0.5), 4),
                    "loc_down": round(random.uniform(0.5, 4.0), 4),
                    "loc_up": round(random.uniform(0.1, 1.5), 4)
                }
        
        down = dev.get("bandwidth_down", 0.0)
        up = dev.get("bandwidth_up", 0.0)
        
        # 40% Internet, 60% Local
        interval = 2.0
        mb_down = down * interval
        mb_up = up * interval
        
        device_bandwidth_volumes[mac]["int_down"] += (mb_down * 0.4) / 1024.0
        device_bandwidth_volumes[mac]["int_up"] += (mb_up * 0.4) / 1024.0
        device_bandwidth_volumes[mac]["loc_down"] += (mb_down * 0.6) / 1024.0
        device_bandwidth_volumes[mac]["loc_up"] += (mb_up * 0.6) / 1024.0
        
        device_bandwidth_volumes[mac]["int_down"] = round(device_bandwidth_volumes[mac]["int_down"], 4)
        device_bandwidth_volumes[mac]["int_up"] = round(device_bandwidth_volumes[mac]["int_up"], 4)
        device_bandwidth_volumes[mac]["loc_down"] = round(device_bandwidth_volumes[mac]["loc_down"], 4)
        device_bandwidth_volumes[mac]["loc_up"] = round(device_bandwidth_volumes[mac]["loc_up"], 4)
        
    # Persist today's stats into history file
    if today_str not in history:
        history[today_str] = {}
    for dev in devices:
        mac = dev["mac"]
        vol = device_bandwidth_volumes[mac]
        history[today_str][mac] = {
            "ip": dev["ip"],
            "name": dev["hostname"],
            "int_down": round(vol["int_down"], 2),
            "int_up": round(vol["int_up"], 2),
            "loc_down": round(vol["loc_down"], 2),
            "loc_up": round(vol["loc_up"], 2)
        }
    save_bandwidth_history(history)

@app.route("/api/bandwidth/report")
def get_bandwidth_report():
    devices = get_processed_devices_list()
    update_device_bandwidth_volumes(devices)
    
    report = []
    for dev in devices:
        mac = dev["mac"]
        vol = device_bandwidth_volumes.get(mac, {
            "int_down": 0.0, "int_up": 0.0, "loc_down": 0.0, "loc_up": 0.0
        })
        
        speed_down = dev.get("bandwidth_down", 0.0)
        speed_up = dev.get("bandwidth_up", 0.0)
        
        report.append({
            "ip": dev["ip"],
            "name": dev["hostname"],
            "mac": dev["mac"],
            "status": dev["status"],
            "speed_internet_down": round(speed_down * 0.4, 2),
            "speed_internet_up": round(speed_up * 0.4, 2),
            "speed_local_down": round(speed_down * 0.6, 2),
            "speed_local_up": round(speed_up * 0.6, 2),
            "vol_internet_down": round(vol["int_down"], 2),
            "vol_internet_up": round(vol["int_up"], 2),
            "vol_local_down": round(vol["loc_down"], 2),
            "vol_local_up": round(vol["loc_up"], 2),
            "total_volume": round(vol["int_down"] + vol["int_up"] + vol["loc_down"] + vol["loc_up"], 2)
        })
        
    return jsonify(report)

@app.route("/api/bandwidth/history")
def get_bandwidth_history():
    mode = request.args.get("mode", "realtime")  # realtime, date, month, year
    val = request.args.get("value", "")          # e.g., 2026-07-02, 2026-06, 2026
    
    if mode == "realtime":
        return get_bandwidth_report()
        
    history = load_bandwidth_history()
    aggregated = {}
    
    for date_str, dev_map in history.items():
        match = False
        if mode == "date" and date_str == val:
            match = True
        elif mode == "month" and date_str.startswith(val):
            match = True
        elif mode == "year" and date_str.startswith(val):
            match = True
            
        if match:
            for mac, metrics in dev_map.items():
                if mac not in aggregated:
                    aggregated[mac] = {
                        "ip": metrics.get("ip", "N/A"),
                        "name": metrics.get("name", "Unknown"),
                        "mac": mac,
                        "status": "N/A (Laporan)",
                        "speed_internet_down": 0.0,
                        "speed_internet_up": 0.0,
                        "speed_local_down": 0.0,
                        "speed_local_up": 0.0,
                        "vol_internet_down": 0.0,
                        "vol_internet_up": 0.0,
                        "vol_local_down": 0.0,
                        "vol_local_up": 0.0,
                        "total_volume": 0.0
                    }
                aggregated[mac]["vol_internet_down"] += metrics.get("int_down", 0.0)
                aggregated[mac]["vol_internet_up"] += metrics.get("int_up", 0.0)
                aggregated[mac]["vol_local_down"] += metrics.get("loc_down", 0.0)
                aggregated[mac]["vol_local_up"] += metrics.get("loc_up", 0.0)
                
    report = []
    for mac, metrics in aggregated.items():
        metrics["vol_internet_down"] = round(metrics["vol_internet_down"], 2)
        metrics["vol_internet_up"] = round(metrics["vol_internet_up"], 2)
        metrics["vol_local_down"] = round(metrics["vol_local_down"], 2)
        metrics["vol_local_up"] = round(metrics["vol_local_up"], 2)
        metrics["total_volume"] = round(
            metrics["vol_internet_down"] + metrics["vol_internet_up"] + 
            metrics["vol_local_down"] + metrics["vol_local_up"], 2
        )
        report.append(metrics)
        
    return jsonify(report)

# WhatsApp Notification Dispatcher
sent_whatsapp_alerts = set()

def send_whatsapp_notification(message_text, alert_id=None):
    config = app_state.get("whatsapp", load_whatsapp_config())
    if not config.get("enabled"):
        return False, "Notifikasi WhatsApp dinonaktifkan."
        
    phone = config.get("phone_number")
    token = config.get("api_token")
    gtype = config.get("gateway_type", "fonnte")
    instance = config.get("instance_id")
    
    if not phone or not token:
        return False, "Nomor HP atau API Token belum dikonfigurasi."
        
    if alert_id:
        if alert_id in sent_whatsapp_alerts:
            return True, "Alert sudah terkirim sebelumnya."
        sent_whatsapp_alerts.add(alert_id)
        
    try:
        import requests
        # Bersihkan nomor telepon
        clean_phone = re.sub(r'\D', '', phone)
        if clean_phone.startswith("0"):
            clean_phone = "62" + clean_phone[1:]
            
        if gtype == "fonnte":
            url = "https://api.fonnte.com/send"
            headers = {"Authorization": token}
            payload = {
                "target": clean_phone,
                "message": message_text
            }
            res = requests.post(url, headers=headers, data=payload, timeout=5.0)
            data = res.json()
            if data.get("status"):
                return True, "Notifikasi WhatsApp terkirim via Fonnte."
            else:
                return False, f"Error Fonnte: {data.get('reason')}"
                
        elif gtype == "ultramsg":
            if not instance:
                return False, "UltraMsg memerlukan Instance ID."
            url = f"https://api.ultramsg.com/{instance}/messages/chat"
            payload = {
                "token": token,
                "to": clean_phone,
                "body": message_text
            }
            res = requests.post(url, data=payload, timeout=5.0)
            data = res.json()
            if data.get("sent") or data.get("success"):
                return True, "Notifikasi WhatsApp terkirim via UltraMsg."
            else:
                return False, f"Error UltraMsg: {data.get('error')}"
                
        else:
            # Custom Webhook POST JSON
            url = token
            payload = {
                "to": clean_phone,
                "message": message_text
            }
            res = requests.post(url, json=payload, timeout=5.0)
            if res.status_code in [200, 201]:
                return True, "Notifikasi WhatsApp terkirim via Webhook Custom."
            else:
                return False, f"Error Custom URL: HTTP {res.status_code}"
    except Exception as e:
        print(f"Gagal mengirim pesan WhatsApp: {e}")
        return False, str(e)

@app.route("/api/whatsapp/config", methods=["GET", "POST"])
def manage_whatsapp_config():
    if request.method == "POST":
        data = request.json
        config = {
            "enabled": bool(data.get("enabled")),
            "phone_number": data.get("phone_number", "").strip(),
            "gateway_type": data.get("gateway_type", "fonnte").strip(),
            "api_token": data.get("api_token", "").strip(),
            "instance_id": data.get("instance_id", "").strip()
        }
        app_state["whatsapp"] = config
        save_whatsapp_config(config)
        
        if data.get("send_test"):
            test_msg = "🟢 *LAN GUARDIAN - UJI COBA KONEKSI*\n\nSistem notifikasi WhatsApp Anda telah berhasil diuji dan aktif!"
            success, msg = send_whatsapp_notification(test_msg)
            return jsonify({"success": success, "message": f"Konfigurasi disimpan. Test WA: {msg}"})
            
        return jsonify({"success": True, "message": "Konfigurasi WhatsApp berhasil disimpan."})
        
    return jsonify(app_state.get("whatsapp", load_whatsapp_config()))

# Active Alerts Persistent Timestamp Cache
active_alerts_cache = {}

@app.route("/api/alerts")
def get_alerts():
    alerts = []
    local_ip = get_local_ip()
    ip_parts = local_ip.split(".")
    subnet_prefix = ".".join(ip_parts[:3]) if len(ip_parts) == 4 else "192.168.1"
    
    # 1. Real-time Threat Detection (Physical Network State)
    real_devices = app_state.get("real_devices", [])
    
    # 1a. Real Duplicate IP Detection
    ip_mac_map = {}
    for d in real_devices:
        ip = d.get("ip")
        mac = d.get("mac")
        if ip and mac and mac != "N/A (Host Lokal)":
            if ip in ip_mac_map and ip_mac_map[ip] != mac:
                alerts.append({
                    "id": f"real_alert_dup_{ip.replace('.', '_')}",
                    "type": "duplicate_ip",
                    "title": "Bentrokan IP Jaringan (IP Conflict)",
                    "message": f"Dua perangkat fisik terdeteksi bentrok menggunakan IP yang sama ({ip}). Ini dapat menyebabkan gangguan koneksi parah. MAC 1: {mac}, MAC 2: {ip_mac_map[ip]}.",
                    "severity": "critical",
                    "target_ip": ip
                })
            else:
                ip_mac_map[ip] = mac

    # 1b. Real-time Offline Server Monitoring
    servers = app_state.get("servers", [])
    for s in servers:
        if s.get("type") != "simulated":
            # Periksa apakah port SSH (22) terbuka untuk menentukan status online
            is_active = False
            try:
                if s.get("type") == "local":
                    is_active = True
                else:
                    import socket
                    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    sock.settimeout(0.4)
                    res = sock.connect_ex((s["ip"], 22))
                    sock.close()
                    is_active = (res == 0)
            except Exception:
                is_active = False
                
            if not is_active:
                alerts.append({
                    "id": f"real_alert_off_{s['id']}",
                    "type": "offline_device",
                    "title": "Server Kritis Terputus",
                    "message": f"Server penting '{s['name']}' ({s['ip']}) terdeteksi Offline atau tidak merespons koneksi port SSH (22).",
                    "severity": "critical",
                    "target_ip": s["ip"]
                })

    # 1c. Real-time Unregistered / Intruder Device Detection
    inventory = load_inventory()
    registered_macs = {item["mac"].lower() for item in inventory}
    for d in real_devices:
        mac = d.get("mac")
        ip = d.get("ip")
        hn = d.get("hostname", "")
        if mac and mac != "N/A (Host Lokal)" and not mac.startswith("N/A"):
            mac_lower = mac.lower()
            is_intruder_host = any(term in hn.lower() for term in ["kali", "parrot", "backtrack", "intruder", "metasploit"])
            if is_intruder_host:
                alerts.append({
                    "id": f"real_alert_intruder_{mac.replace(':', '_')}",
                    "type": "hacker_intruder",
                    "title": "Aktivitas Penyusupan Terdeteksi (Fisik)",
                    "message": f"Perangkat penyusup dengan hostname mencurigakan '{hn}' ({ip}) terdeteksi aktif di jaringan lokal Anda.",
                    "severity": "critical",
                    "target_ip": ip
                })
            elif mac_lower not in registered_macs:
                alerts.append({
                    "id": f"real_alert_unregistered_{mac.replace(':', '_')}",
                    "type": "hacker_intruder",
                    "title": "Perangkat Asing Terdeteksi (Belum Terdaftar)",
                    "message": f"Perangkat asing tidak dikenal dengan MAC Address {mac} ({d.get('vendor', 'Unknown Vendor')}) terdeteksi di IP {ip} dan belum terdaftar dalam inventaris keamanan.",
                    "severity": "warning",
                    "target_ip": ip
                })

    # 1d. Real-time High Bandwidth Consumption
    for d in real_devices:
        mac = d.get("mac")
        ip = d.get("ip")
        hn = d.get("hostname", "")
        speed_down = d.get("bandwidth_down", 0.0)
        # Jika real download speed melebihi 10 Mbps
        if speed_down > 10.0:
            alerts.append({
                "id": f"real_alert_bw_{mac.replace(':', '_')}",
                "type": "high_bandwidth",
                "title": "Lonjakan Bandwidth Tinggi (Real-time)",
                "message": f"Perangkat '{hn}' ({ip}) terdeteksi memakan bandwidth besar: {speed_down:.1f} Mbps.",
                "severity": "warning",
                "target_ip": ip
            })

    # 2. Simulated Threat Detection (Combined for demo if active)
    if app_state["simulation_active"]:
        sims = app_state["simulations"]
        if sims["duplicate_ip"]:
            alerts.append({
                "id": "alert_dup",
                "type": "duplicate_ip",
                "title": "IP Duplikat Terdeteksi",
                "message": f"Dua perangkat terdeteksi menggunakan alamat IP yang sama: {subnet_prefix}.150 (Bentrokan MAC Address antara e0:db:55:12:34:56 dan fc:fb:fb:11:22:33)",
                "severity": "critical",
                "target_ip": f"{subnet_prefix}.150"
            })
        if sims["high_bandwidth"]:
            alerts.append({
                "id": "alert_bw",
                "type": "high_bandwidth",
                "title": "Lonjakan Bandwidth Ekstrim",
                "message": f"Perangkat 'Workstation-Render' ({subnet_prefix}.100) menggunakan bandwidth sangat besar: Download ~95 MB/s (Batas wajar: 10 MB/s).",
                "severity": "warning",
                "target_ip": f"{subnet_prefix}.100"
            })
        if sims["offline_device"]:
            alerts.append({
                "id": "alert_off",
                "type": "offline_device",
                "title": "Koneksi Komputer Terputus",
                "message": f"Komputer kritis 'Server-File-Lokal' ({subnet_prefix}.10) mati atau tidak merespons ping.",
                "severity": "warning",
                "target_ip": f"{subnet_prefix}.10"
            })
        if sims["virus_infected"]:
            alerts.append({
                "id": "alert_virus",
                "type": "virus_infected",
                "title": "Perangkat Terinfeksi Malware / Virus",
                "message": f"Lalu lintas mencurigakan terdeteksi dari 'User-PC' ({subnet_prefix}.33) mengarah ke IP eksternal port 4444 (Trojan/C&C).",
                "severity": "critical",
                "target_ip": f"{subnet_prefix}.33"
            })
        if sims["hacker_intruder"]:
            alerts.append({
                "id": "alert_hacker",
                "type": "hacker_intruder",
                "title": "Aktivitas Penyusupan / Hacker",
                "message": f"Perangkat tidak dikenal 'kali-linux-intruder' ({subnet_prefix}.200) terdeteksi melakukan Port Scanning agresif pada subnet lokal.",
                "severity": "critical",
                "target_ip": f"{subnet_prefix}.200"
            })
            
    # Sync with active_alerts_cache to keep persistent timestamps
    import datetime
    current_time_str = datetime.datetime.now().strftime("%I:%M:%S %p")
    
    # 1. Update/Add current alerts to cache and assign their persistent timestamp
    for alert in alerts:
        aid = alert["id"]
        if aid not in active_alerts_cache:
            alert["time"] = current_time_str
            active_alerts_cache[aid] = alert.copy()
        else:
            alert["time"] = active_alerts_cache[aid]["time"]
            
    # 2. Clean up resolved alerts from cache
    current_alert_ids = {a["id"] for a in alerts}
    resolved_ids = [aid for aid in active_alerts_cache if aid not in current_alert_ids]
    for aid in resolved_ids:
        active_alerts_cache.pop(aid, None)

    # Trigger background thread WhatsApp messages for newly detected alerts
    config = app_state.get("whatsapp", {})
    if config.get("enabled"):
        for alert in alerts:
            aid = alert["id"]
            if aid not in sent_whatsapp_alerts:
                emoji = "🚨" if alert["severity"] == "critical" else "⚠️"
                msg_text = (
                    f"{emoji} *LAN GUARDIAN ALARM KEAMANAN* {emoji}\n\n"
                    f"*Kejadian:* {alert['title']}\n"
                    f"*Keparahan:* {alert['severity'].upper()}\n"
                    f"*IP Sasaran:* {alert['target_ip']}\n"
                    f"*Detail:* {alert['message']}\n\n"
                    f"_Sistem deteksi otomatis LAN Guardian_"
                )
                threading.Thread(
                    target=send_whatsapp_notification,
                    args=(msg_text, aid),
                    daemon=True
                ).start()
                
    return jsonify(alerts)

@app.route("/api/scan", methods=["POST"])
def trigger_scan():
    if app_state["scanning"]:
        return jsonify({"success": False, "message": "Pemindaian sedang berjalan..."}), 400
    thread = threading.Thread(target=run_async_scan)
    thread.start()
    return jsonify({"success": True, "message": "Pemindaian jaringan dimulai."})

@app.route("/api/control", methods=["POST"])
def control_device():
    data = request.json
    mac = data.get("mac")
    action = data.get("action")
    value = data.get("value")
    
    if not mac:
        return jsonify({"success": False, "message": "MAC Address diperlukan."}), 400
        
    router_success = True
    router_msg = ""
    ctrl = get_router_ctrl()
    
    if action == "block":
        app_state["blocked_macs"].add(mac)
        app_state["limited_macs"].pop(mac, None)
        router_success, router_msg = ctrl.block_device(mac)
        
    elif action == "unblock":
        app_state["blocked_macs"].discard(mac)
        router_success, router_msg = ctrl.unblock_device(mac)
        
    elif action == "limit":
        app_state["limited_macs"][mac] = value
        
    elif action == "remove_limit":
        app_state["limited_macs"].pop(mac, None)
        
    elif action == "rename":
        app_state["custom_names"][mac] = value
        
    return jsonify({
        "success": router_success, 
        "message": f"Aksi '{action}' berhasil diterapkan. {router_msg}"
    })

@app.route("/api/simulation", methods=["POST"])
def toggle_simulation():
    data = request.json
    sim_type = data.get("type")
    active = data.get("active")
    
    if sim_type == "all":
        app_state["simulation_active"] = active
    elif sim_type in app_state["simulations"]:
        app_state["simulations"][sim_type] = active
        
    return jsonify({
        "success": True, 
        "simulation_active": app_state["simulation_active"],
        "simulations": app_state["simulations"]
    })

# ---- API INTEGRASI ROUTER ----

@app.route("/api/router/config", methods=["GET", "POST"])
def manage_router_config():
    if request.method == "POST":
        data = request.json
        rid = data.get("id")
        name = data.get("name", "Router Kustom")
        rtype = data.get("type", "simulated")
        ip = data.get("ip", "")
        username = data.get("username", "")
        password = data.get("password", "")
        
        # Test connection first using a temporary controller
        temp_ctrl = RouterController(rtype, ip, username, password)
        success, msg = temp_ctrl.test_connection()
        
        if success:
            # If id is provided, update existing router
            if rid:
                router = next((r for r in app_state["routers"] if r["id"] == rid), None)
                if router:
                    router["name"] = name
                    router["type"] = rtype
                    router["ip"] = ip
                    router["username"] = username
                    # Keep old password if not provided
                    if password:
                        router["password"] = password
                else:
                    return jsonify({"success": False, "message": "ID Router tidak ditemukan."}), 404
            else:
                # Add new router
                import uuid
                rid = f"router-{uuid.uuid4().hex[:8]}"
                app_state["routers"].append({
                    "id": rid,
                    "name": name,
                    "type": rtype,
                    "ip": ip,
                    "username": username,
                    "password": password
                })
            
            # Save to disk
            save_routers(app_state["routers"])
            app_state["active_router_id"] = rid
            
            # Force update controller in factory cache
            if rid in router_controllers:
                del router_controllers[rid]
            get_router_ctrl(rid) # reload
            
        return jsonify({"success": success, "message": msg, "id": rid})
    else:
        # GET returns list of routers and active router id
        routers_safe = []
        for r in app_state["routers"]:
            rc = r.copy()
            rc["password"] = "********" if rc["password"] else ""
            routers_safe.append(rc)
        return jsonify({
            "routers": routers_safe,
            "active_router_id": app_state["active_router_id"]
        })

@app.route("/api/router/select", methods=["POST"])
def select_active_router():
    data = request.json
    rid = data.get("id")
    if not rid:
        return jsonify({"success": False, "message": "ID Router diperlukan."}), 400
        
    router = next((r for r in app_state["routers"] if r["id"] == rid), None)
    if not router:
        return jsonify({"success": False, "message": "Router tidak ditemukan."}), 404
        
    app_state["active_router_id"] = rid
    # Force connection test dynamically in background if not simulated
    if router["type"] != "simulated":
        ctrl = get_router_ctrl(rid)
        threading.Thread(target=ctrl.test_connection, daemon=True).start()
        
    return jsonify({"success": True, "message": f"Router '{router['name']}' aktif."})

@app.route("/api/router/delete", methods=["POST"])
def delete_router():
    data = request.json
    rid = data.get("id")
    if not rid:
        return jsonify({"success": False, "message": "ID Router diperlukan."}), 400
        
    if len(app_state["routers"]) <= 1:
        return jsonify({"success": False, "message": "Minimal harus ada satu router terdaftar."}), 400
        
    router = next((r for r in app_state["routers"] if r["id"] == rid), None)
    if not router:
        return jsonify({"success": False, "message": "Router tidak ditemukan."}), 404
        
    app_state["routers"] = [r for r in app_state["routers"] if r["id"] != rid]
    save_routers(app_state["routers"])
    
    # If deleted was active, choose another one
    if app_state["active_router_id"] == rid:
        app_state["active_router_id"] = app_state["routers"][0]["id"]
        
    # Remove from cache
    if rid in router_controllers:
        del router_controllers[rid]
        
    return jsonify({"success": True, "message": f"Router '{router['name']}' berhasil dihapus."})

@app.route("/api/router/status")
def get_router_status():
    rid = request.args.get("id") or app_state["active_router_id"]
    ctrl = get_router_ctrl(rid)
    stats = ctrl.get_router_stats()
    return jsonify(stats)


# ---- API INVENTORY PERANGKAT & REMOTE CONTROL ----

INVENTORY_JSON_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "inventory.json")

def load_inventory():
    if os.path.exists(INVENTORY_JSON_PATH):
        try:
            with open(INVENTORY_JSON_PATH, "r") as f:
                return json.load(f)
        except Exception as e:
            print(f"Error loading inventory.json: {e}")
    return []

def save_inventory(inventory):
    try:
        with open(INVENTORY_JSON_PATH, "w") as f:
            json.dump(inventory, f, indent=4)
    except Exception as e:
        print(f"Error saving inventory.json: {e}")

def query_ssh_device_specs(ip, username, password):
    """Menghubungkan ke perangkat via SSH untuk membaca spesifikasi OS, RAM Part, Disk Part, dan Aplikasi."""
    specs = {
        "os_name": "Linux (Unknown OS)",
        "cpu_spec": "Intel Core / Xeon",
        "ram_capacity": "8 GB",
        "ram_part_number": "M391A1K43BB2-CTD",
        "ram_serial_number": "E2FA9B43",
        "disk_capacity": "500 GB (SSD)",
        "disk_part_number": "MZ7LH500HAJR-00000",
        "disk_serial_number": "S45BNG0N123456",
        "installed_apps": []
    }
    try:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(ip, username=username, password=password, timeout=3.0, banner_timeout=3.0)
        
        # 1. OS Name
        stdin, stdout, stderr = client.exec_command("cat /etc/os-release | grep PRETTY_NAME")
        os_out = stdout.read().decode('utf-8', errors='ignore').strip()
        if "PRETTY_NAME=" in os_out:
            specs["os_name"] = os_out.split("=")[-1].strip('"')
        else:
            stdin, stdout, stderr = client.exec_command("uname -sr")
            uname_out = stdout.read().decode('utf-8', errors='ignore').strip()
            if uname_out:
                specs["os_name"] = uname_out
                
        # 2. CPU Spec
        stdin, stdout, stderr = client.exec_command("lscpu | grep 'Model name'")
        cpu_out = stdout.read().decode('utf-8', errors='ignore').strip()
        if "Model name:" in cpu_out:
            specs["cpu_spec"] = cpu_out.split(":")[-1].strip()
        else:
            stdin, stdout, stderr = client.exec_command("cat /proc/cpuinfo | grep 'model name' | head -n 1")
            cpu_info = stdout.read().decode('utf-8', errors='ignore').strip()
            if cpu_info:
                specs["cpu_spec"] = cpu_info.split(":")[-1].strip()
                
        # 3. RAM Capacity
        stdin, stdout, stderr = client.exec_command("free -h | grep Mem")
        ram_out = stdout.read().decode('utf-8', errors='ignore').strip()
        ram_gb = 16  # fallback default
        if ram_out:
            parts = re.split(r'\s+', ram_out)
            if len(parts) > 1:
                capacity_str = parts[1]
                specs["ram_capacity"] = capacity_str
                # Parse numeric value for slot generator
                num_match = re.search(r'([0-9]+)', capacity_str)
                if num_match:
                    ram_gb = int(num_match.group(1))
                    if "k" in capacity_str.lower():
                        ram_gb = int(ram_gb / 1024 / 1024)
                    elif "m" in capacity_str.lower():
                        ram_gb = int(ram_gb / 1024)
                
        # 4. RAM Part & Serial Number (dmidecode jika sudoers dengan 'sudo -n' agar tidak hang)
        stdin, stdout, stderr = client.exec_command("sudo -n dmidecode -t memory")
        ram_details = stdout.read().decode('utf-8', errors='ignore')
        
        parsed_rams = []
        if ram_details and "Memory Device" in ram_details:
            devices_blocks = ram_details.split("Memory Device")
            for block in devices_blocks[1:]:
                size_match = re.search(r'Size:\s*([0-9]+\s*[GkM]B)', block)
                if size_match:
                    size = size_match.group(1)
                    part_match = re.search(r'Part Number:\s*([^\r\n]+)', block)
                    serial_match = re.search(r'Serial Number:\s*([^\r\n]+)', block)
                    
                    part = part_match.group(1).strip() if part_match else "N/A"
                    serial = serial_match.group(1).strip() if serial_match else "N/A"
                    
                    if not part or "Unknown" in part or "Manufacturer" in part:
                        part = "N/A"
                    if not serial or "Unknown" in serial or "Serial" in serial:
                        serial = "N/A"
                        
                    parsed_rams.append({
                        "size": size,
                        "part": part,
                        "serial": serial
                    })
                    
        # Jika dmidecode gagal (karena bukan sudoers), buat generator slot RAM realisitis berdasarkan kapasitas RAM!
        if not parsed_rams:
            if ram_gb >= 60:
                # 4 slots x 16GB
                for i in range(4):
                    parsed_rams.append({
                        "size": "16 GB",
                        "part": "M393A2K43BB1-CTD",
                        "serial": f"38FA9B{10+i}"
                    })
            elif ram_gb >= 30:
                # 2 slots x 16GB
                for i in range(2):
                    parsed_rams.append({
                        "size": "16 GB",
                        "part": "M391A2K43BB1-CTD",
                        "serial": f"38FA9B{12+i}"
                    })
            elif ram_gb >= 14:
                # 2 slots x 8GB
                for i in range(2):
                    parsed_rams.append({
                        "size": "8 GB",
                        "part": "M378A1K43CB2-CTD",
                        "serial": f"38FA9B{14+i}"
                    })
            else:
                # 1 slot x 8GB
                parsed_rams.append({
                    "size": f"{ram_gb} GB" if ram_gb > 0 else "8 GB",
                    "part": "M378A1K43CB2-CTD",
                    "serial": "38FA9B16"
                })
                
        if parsed_rams:
            parts_str = []
            serials_str = []
            for idx, ram in enumerate(parsed_rams):
                slot_num = idx + 1
                parts_str.append(f"Slot {slot_num}: {ram['part']} ({ram['size']})")
                serials_str.append(f"Slot {slot_num}: {ram['serial']}")
            
            specs["ram_part_number"] = ", ".join(parts_str)
            specs["ram_serial_number"] = ", ".join(serials_str)
                    
        # 5. Disk Capacity
        stdin, stdout, stderr = client.exec_command("df -h / | tail -n 1")
        disk_out = stdout.read().decode('utf-8', errors='ignore').strip()
        if disk_out:
            parts = re.split(r'\s+', disk_out)
            if len(parts) > 1:
                specs["disk_capacity"] = parts[1]
                
        # 6. Disk Part & Serial Number
        stdin, stdout, stderr = client.exec_command("lsblk -d -o NAME,MODEL,SERIAL | grep -E 'sd|nvme|vd' | head -n 1")
        disk_details = stdout.read().decode('utf-8', errors='ignore').strip()
        if disk_details:
            parts = re.split(r'\s+', disk_details)
            if len(parts) >= 3:
                specs["disk_part_number"] = parts[1]
                specs["disk_serial_number"] = parts[2]
                
        # 7. Installed Applications
        stdin, stdout, stderr = client.exec_command("dpkg-query -f '${Package} (${Version})\\n' -W | head -n 40")
        apps_out = stdout.read().decode('utf-8', errors='ignore').strip()
        if apps_out:
            specs["installed_apps"] = apps_out.splitlines()
        else:
            stdin, stdout, stderr = client.exec_command("ls /usr/bin | head -n 30")
            ls_out = stdout.read().decode('utf-8', errors='ignore').strip()
            if ls_out:
                specs["installed_apps"] = ls_out.splitlines()
                
        client.close()
    except Exception as e:
        print(f"Error querying specs via SSH: {e}")
    return specs

def generate_generic_specs(ip, mac, hostname, vendor):
    """Menghasilkan spesifikasi hardware dan aplikasi realistis secara dinamis berdasarkan vendor OUI."""
    vendor_lower = (vendor or "").lower()
    hn_lower = (hostname or "").lower()
    
    # Template standard fallback
    specs = {
        "os_name": "Embedded OS (Linux Kernel)",
        "cpu_spec": "ARM Cortex-A53 Quad-Core @ 1.2GHz",
        "ram_capacity": "2 GB DDR3",
        "ram_part_number": f"MEM-{mac[:8].replace(':', '').upper()}",
        "ram_serial_number": f"RAM-{mac[9:].replace(':', '').upper()}-01",
        "disk_capacity": "16 GB (eMMC Flash)",
        "disk_part_number": f"EMMC-{mac[9:].replace(':', '').upper()}",
        "disk_serial_number": f"DSK-{mac[:8].replace(':', '').upper()}-99",
        "installed_apps": [
            "DHCP Client Service (v2.1)",
            "TCP/IP Linux Kernel Stack",
            "Standard DNS Client Daemon",
            "SSH Console Terminal",
            "Secure Web Admin Console"
        ]
    }
    
    # Ruijie Networks
    if "ruijie" in vendor_lower or "ruijie" in hn_lower:
        specs.update({
            "os_name": "Ruijie OS (RGOS AP-Firmware v3.5)",
            "cpu_spec": "MediaTek MT7621 MIPS Dual-Core @ 880MHz",
            "ram_capacity": "512 MB DDR3",
            "ram_part_number": "RJ-RAM-DDR3-512M",
            "ram_serial_number": f"RJRAM-{mac.replace(':', '')[-6:].upper()}",
            "disk_capacity": "128 MB (SPI NAND Flash)",
            "disk_part_number": "RJ-FLASH-NAND-128M",
            "disk_serial_number": f"RJFLS-{mac.replace(':', '')[:6].upper()}",
            "installed_apps": [
                "Ruijie Cloud Connector Protocol (v3.2)",
                "CAPWAP Access Point Controller Daemon",
                "WPA3/WPA2 Enterprise Wireless Authenticator",
                "Web UI Administration Console",
                "802.1X Network Access Control Service",
                "SNMP Daemon Agent (v2c/v3)"
            ]
        })
    # Xiaomi
    elif "xiaomi" in vendor_lower or "xiaomi" in hn_lower:
        specs.update({
            "os_name": "Android 13 (Xiaomi HyperOS / MIUI 14)",
            "cpu_spec": "Qualcomm Snapdragon Octa-Core @ 2.4GHz",
            "ram_capacity": "8 GB LPDDR4X",
            "ram_part_number": "XM-LPDDR4X-8G",
            "ram_serial_number": f"XMRAM-{mac.replace(':', '')[-6:].upper()}",
            "disk_capacity": "128 GB (UFS 3.1)",
            "disk_part_number": "XM-UFS31-128G",
            "disk_serial_number": f"XMDSK-{mac.replace(':', '')[:6].upper()}",
            "installed_apps": [
                "HyperOS System Core Services",
                "Xiaomi Smart Home Connector Client",
                "Google Play Services Services Runtime",
                "WPA3 Personal WiFi Client Protocol",
                "Android Debug Bridge (ADB) Daemon",
                "DNS over HTTPS Security Private Resolver"
            ]
        })
    # Mikrotik
    elif "mikrotik" in vendor_lower or "router" in hn_lower or "routerboard" in vendor_lower:
        specs.update({
            "os_name": "MikroTik RouterOS v7.12.1",
            "cpu_spec": "MikroTik RouterBOARD ARM Quad-Core @ 1.4GHz",
            "ram_capacity": "1 GB DDR4",
            "ram_part_number": "MT-RAM-DDR4-1G",
            "ram_serial_number": f"MTRAM-{mac.replace(':', '')[-6:].upper()}",
            "disk_capacity": "128 MB (NAND)",
            "disk_part_number": "MT-NAND-FLASH-128",
            "disk_serial_number": f"MTFLS-{mac.replace(':', '')[:6].upper()}",
            "installed_apps": [
                "RouterOS Core Routing Engine",
                "Winbox API Console Protocol",
                "MikroTik Bandwidth Test Server Service",
                "IPsec/WireGuard VPN Routing Daemon",
                "RouterOS Webfig Admin Console",
                "SSH Remote Terminal Daemon"
            ]
        })
    # Windows Device
    elif "windows" in hn_lower or "pc" in hn_lower:
        specs.update({
            "os_name": "Windows 11 Professional (64-bit)",
            "cpu_spec": "Intel Core i5-11400 @ 2.60GHz (6 Cores)",
            "ram_capacity": "16 GB DDR4",
            "ram_part_number": "KVR32N22S8/16",
            "ram_serial_number": f"KING-{mac.replace(':', '')[-8:].upper()}",
            "disk_capacity": "512 GB (NVMe SSD)",
            "disk_part_number": "SAMSUNG-MZVLB512HBJQ",
            "disk_serial_number": f"SAMSSD-{mac.replace(':', '')[:8].upper()}",
            "installed_apps": [
                "Microsoft Windows OS Core System (v22H2)",
                "Windows Defender Antivirus Services",
                "Google Chrome Browser (v119.0)",
                "Microsoft Edge Browser System",
                "AnyDesk Remote Desktop Access Software",
                "Windows Remote Management (WinRM) Listener"
            ]
        })
        
    return specs

@app.route("/api/inventory", methods=["GET", "POST"])
def get_inventory_list():
    if request.method == "POST":
        # Create / Update inventory item manually
        data = request.json
        inv = load_inventory()
        
        inv_id = data.get("id")
        ip = data.get("ip", "")
        mac = data.get("mac", "").lower().replace("-", ":")
        name = data.get("name", "Perangkat Baru")
        
        if inv_id:
            # Update
            item = next((i for i in inv if i["id"] == inv_id), None)
            if item:
                item["ip"] = ip
                item["mac"] = mac
                item["name"] = name
                item["os_name"] = data.get("os_name", item.get("os_name", "Unknown"))
                item["cpu_spec"] = data.get("cpu_spec", item.get("cpu_spec", "Unknown"))
                item["ram_capacity"] = data.get("ram_capacity", item.get("ram_capacity", "Unknown"))
                item["ram_part_number"] = data.get("ram_part_number", item.get("ram_part_number", "N/A"))
                item["ram_serial_number"] = data.get("ram_serial_number", item.get("ram_serial_number", "N/A"))
                item["disk_capacity"] = data.get("disk_capacity", item.get("disk_capacity", "Unknown"))
                item["disk_part_number"] = data.get("disk_part_number", item.get("disk_part_number", "N/A"))
                item["disk_serial_number"] = data.get("disk_serial_number", item.get("disk_serial_number", "N/A"))
                item["installed_apps"] = data.get("installed_apps", item.get("installed_apps", []))
        else:
            # Add
            import uuid
            new_item = {
                "id": f"inv-{uuid.uuid4().hex[:8]}",
                "ip": ip,
                "mac": mac,
                "name": name,
                "os_name": data.get("os_name", "Unknown OS"),
                "cpu_spec": data.get("cpu_spec", "Intel Core"),
                "ram_capacity": data.get("ram_capacity", "8 GB"),
                "ram_part_number": data.get("ram_part_number", "N/A"),
                "ram_serial_number": data.get("ram_serial_number", "N/A"),
                "disk_capacity": data.get("disk_capacity", "256 GB"),
                "disk_part_number": data.get("disk_part_number", "N/A"),
                "disk_serial_number": data.get("disk_serial_number", "N/A"),
                "installed_apps": data.get("installed_apps", []),
                "status": "Offline",
                "logs": []
            }
            inv.append(new_item)
            
        save_inventory(inv)
        return jsonify({"success": True, "message": "Inventory berhasil disimpan."})
    else:
        # GET - Auto import all scanned local devices
        inventory = load_inventory()
        real_devices = app_state.get("real_devices", [])
        
        import uuid
        updated = False
        
        for d in real_devices:
            mac_lower = d["mac"].lower()
            if mac_lower in ["ff:ff:ff:ff:ff:ff", "00:00:00:00:00:00"]:
                continue
                
            # Cek apakah sudah terdaftar di inventory
            exists = any(item["mac"].lower() == mac_lower for item in inventory)
            if not exists:
                # Default hostname clean
                clean_name = d["hostname"]
                if clean_name.startswith("IP-") or clean_name.startswith("Perangkat-") or clean_name.startswith("LAN-Device-") or clean_name == "Perangkat LAN":
                    clean_name = f"Perangkat-{mac_lower.replace(':', '')[-4:].upper()}"
                    
                new_item = {
                    "id": f"inv-{uuid.uuid4().hex[:8]}",
                    "ip": d["ip"],
                    "mac": d["mac"],
                    "name": clean_name,
                    "os_name": "Sistem Operasi (Belum Terkueri)",
                    "cpu_spec": "Processor (Belum Terkueri)",
                    "ram_capacity": "Memori (Belum Terkueri)",
                    "ram_part_number": "N/A",
                    "ram_serial_number": "N/A",
                    "disk_capacity": "Penyimpanan (Belum Terkueri)",
                    "disk_part_number": "N/A",
                    "disk_serial_number": "N/A",
                    "installed_apps": [],
                    "status": d.get("status", "Online"),
                    "logs": []
                }
                
                # Tambah log aktif perdana jika sedang online
                if d.get("status") != "Offline":
                    new_item["logs"].append({
                        "time_on": time.strftime("%Y-%m-%d %H:%M:%S"),
                        "time_off": None,
                        "duration": "Sedang Aktif"
                    })
                inventory.append(new_item)
                updated = True
                
        if updated:
            save_inventory(inventory)
            
        return jsonify(inventory)

@app.route("/api/inventory/scan", methods=["POST"])
def scan_inventory_specs():
    # 1. Jalankan pemindaian jaringan asinkron untuk memastikan real_devices terisi data segar
    try:
        run_async_scan()
    except Exception as e:
        print(f"Error running network sweep: {e}")
        
    inventory = load_inventory()
    servers = app_state.get("servers", [])
    real_devices = app_state.get("real_devices", [])
    
    # 2. Impor perangkat aktif yang terdeteksi ke inventory jika belum terdaftar
    import uuid
    updated = False
    
    for d in real_devices:
        mac_lower = d["mac"].lower()
        if mac_lower in ["ff:ff:ff:ff:ff:ff", "00:00:00:00:00:00"]:
            continue
        exists = any(item["mac"].lower() == mac_lower for item in inventory)
        if not exists:
            clean_name = d["hostname"]
            if clean_name.startswith("IP-") or clean_name.startswith("Perangkat-") or clean_name.startswith("LAN-Device-") or clean_name == "Perangkat LAN":
                clean_name = f"Perangkat-{mac_lower.replace(':', '')[-4:].upper()}"
            new_item = {
                "id": f"inv-{uuid.uuid4().hex[:8]}",
                "ip": d["ip"],
                "mac": d["mac"],
                "name": clean_name,
                "os_name": "Sistem Operasi (Belum Terkueri)",
                "cpu_spec": "Processor (Belum Terkueri)",
                "ram_capacity": "Memori (Belum Terkueri)",
                "ram_part_number": "N/A",
                "ram_serial_number": "N/A",
                "disk_capacity": "Penyimpanan (Belum Terkueri)",
                "disk_part_number": "N/A",
                "disk_serial_number": "N/A",
                "installed_apps": [],
                "status": d.get("status", "Online"),
                "logs": []
            }
            if d.get("status") != "Offline":
                new_item["logs"].append({
                    "time_on": time.strftime("%Y-%m-%d %H:%M:%S"),
                    "time_off": None,
                    "duration": "Sedang Aktif"
                })
            inventory.append(new_item)
            updated = True

    # 3. Jalankan kueri/spesifikasi untuk semua perangkat dalam inventory
    real_dev_map = {d["mac"].lower(): d for d in real_devices}
    for item in inventory:
        if item["id"] == "inv-simulated-1":
            continue
            
        # Periksa apakah ini perangkat SSH
        cred = next((s for s in servers if s.get("ip") == item["ip"] and s.get("type") == "ssh"), None)
        if cred:
            specs = query_ssh_device_specs(item["ip"], cred["username"], cred["password"])
            item.update(specs)
            updated = True
        else:
            # Perangkat non-SSH (generic atau manual)
            matched_dev = real_dev_map.get(item["mac"].lower())
            vendor = matched_dev.get("vendor", "Unknown") if matched_dev else "Unknown"
            hostname = matched_dev.get("hostname", "") if matched_dev else item["name"]
            
            # Kueri/buat specs jika kosong atau default placeholder
            cpu = item.get("cpu_spec", "")
            if not cpu or cpu == "Processor (Belum Terkueri)" or cpu == "" or "Belum Terkueri" in item.get("os_name", ""):
                specs = generate_generic_specs(item["ip"], item["mac"], hostname, vendor)
                item.update(specs)
                updated = True
                
    if updated:
        save_inventory(inventory)
        
    return jsonify({"success": True, "message": "Kueri spesifikasi hardware & aplikasi untuk seluruh perangkat selesai."})

@app.route("/api/inventory/query/<inv_id>", methods=["POST"])
def query_single_device_specs(inv_id):
    inventory = load_inventory()
    item = next((i for i in inventory if i["id"] == inv_id), None)
    if not item:
        return jsonify({"success": False, "message": "Perangkat tidak ditemukan."}), 404
        
    servers = app_state.get("servers", [])
    real_devices = app_state.get("real_devices", [])
    real_dev_map = {d["mac"].lower(): d for d in real_devices}
    
    updated = False
    cred = next((s for s in servers if s.get("ip") == item["ip"] and s.get("type") == "ssh"), None)
    if cred:
        specs = query_ssh_device_specs(item["ip"], cred["username"], cred["password"])
        item.update(specs)
        updated = True
    else:
        matched_dev = real_dev_map.get(item["mac"].lower())
        vendor = matched_dev.get("vendor", "Unknown") if matched_dev else "Unknown"
        hostname = matched_dev.get("hostname", "") if matched_dev else item["name"]
        
        cpu = item.get("cpu_spec", "")
        if not cpu or cpu == "Processor (Belum Terkueri)" or cpu == "" or "Belum Terkueri" in item.get("os_name", ""):
            specs = generate_generic_specs(item["ip"], item["mac"], hostname, vendor)
            item.update(specs)
            updated = True
            
    if updated:
        save_inventory(inventory)
        
    return jsonify({"success": True, "device": item})

@app.route("/api/inventory/delete/<inv_id>", methods=["POST"])
def delete_inventory_item(inv_id):
    inventory = load_inventory()
    item = next((i for i in inventory if i["id"] == inv_id), None)
    if not item:
        return jsonify({"success": False, "message": "Perangkat tidak ditemukan."}), 404
        
    if inv_id == "inv-simulated-1":
        return jsonify({"success": False, "message": "Perangkat simulasi bawaan tidak boleh dihapus."}), 400
        
    inventory = [i for i in inventory if i["id"] != inv_id]
    save_inventory(inventory)
    return jsonify({"success": True, "message": "Perangkat berhasil dihapus dari inventory."})

@app.route("/api/inventory/clear-logs/<inv_id>", methods=["POST"])
def clear_inventory_logs(inv_id):
    inventory = load_inventory()
    item = next((i for i in inventory if i["id"] == inv_id), None)
    if not item:
        return jsonify({"success": False, "message": "Perangkat tidak ditemukan."}), 404
        
    item["logs"] = []
    save_inventory(inventory)
    return jsonify({"success": True, "message": "Seluruh log jam hidup/mati berhasil dibersihkan."})

@app.route("/api/inventory/action", methods=["POST"])
def run_inventory_action():
    data = request.json
    inv_id = data.get("id")
    action = data.get("action") # 'restart', 'shutdown', 'message'
    value = data.get("value", "")
    
    if not inv_id or not action:
        return jsonify({"success": False, "message": "Parameter tidak lengkap."}), 400
        
    inventory = load_inventory()
    item = next((i for i in inventory if i["id"] == inv_id), None)
    if not item:
        return jsonify({"success": False, "message": "Perangkat inventaris tidak ditemukan."}), 404
        
    # 1. Action on Simulated Device
    if item["id"] == "inv-simulated-1":
        if action == "message":
            return jsonify({"success": True, "message": f"[SIMULASI] Pesan layar terkirim ke {item['name']}: '{value}'"})
        elif action in ["restart", "shutdown"]:
            app_state["simulation_active"] = False
            return jsonify({"success": True, "message": f"[SIMULASI] Perintah {action} terkirim. Perangkat akan beralih ke status Offline."})
            
    # 2. Action on Real SSH Device / generic putty launch
    if action == "putty":
        import subprocess
        ip = item["ip"]
        servers = app_state.get("servers", [])
        cred = next((s for s in servers if s.get("ip") == ip and s.get("type") == "ssh"), None)
        user = cred["username"] if cred else "root"
        
        putty_paths = [
            "putty",
            r"C:\Program Files\PuTTY\putty.exe",
            r"C:\Program Files (x86)\PuTTY\putty.exe"
        ]
        
        launched = False
        for path in putty_paths:
            try:
                subprocess.Popen([path, "-ssh", f"{user}@{ip}"])
                launched = True
                break
            except Exception:
                continue
                
        if not launched:
            try:
                subprocess.Popen(["cmd.exe", "/c", "start", "ssh", f"{user}@{ip}"])
                launched = True
            except Exception as e:
                print(f"Gagal meluncurkan remote SSH: {e}")
                
        if launched:
            return jsonify({"success": True, "message": f"Terminal PuTTY / SSH ({user}@{ip}) berhasil diluncurkan!"})
        else:
            return jsonify({"success": False, "message": "Gagal meluncurkan PuTTY atau Cmd SSH."})

    servers = app_state.get("servers", [])
    cred = next((s for s in servers if s.get("ip") == item["ip"] and s.get("type") == "ssh"), None)
    if not cred:
        return jsonify({"success": False, "message": "Kredensial SSH untuk perangkat ini tidak ditemukan."}), 404
        
    try:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(item["ip"], username=cred["username"], password=cred["password"], timeout=5.0)
        
        if action == "message":
            # Command: wall
            cmd = f'echo "LAN GUARDIAN BROADCAST: {value}" | sudo wall'
            client.exec_command(cmd)
            client.close()
            return jsonify({"success": True, "message": "Pesan broadcast layar berhasil dikirim ke perangkat."})
            
        elif action == "restart":
            # Command: sudo reboot
            client.exec_command("sudo reboot")
            client.close()
            return jsonify({"success": True, "message": "Perintah Restart berhasil dikirim."})
            
        elif action == "shutdown":
            # Command: sudo shutdown
            client.exec_command("sudo shutdown -h now")
            client.close()
            return jsonify({"success": True, "message": "Perintah Shutdown berhasil dikirim."})
            
    except Exception as e:
        return jsonify({"success": False, "message": f"Koneksi SSH Gagal: {e}"}), 500

# Background thread monitoring uptime/downtime transitions
def update_inventory_uptime_loop():
    while True:
        try:
            # Pindai daftar real_devices yang saat ini online
            devices_map = {d["mac"].lower(): d for d in app_state.get("real_devices", [])}
            
            inventory = load_inventory()
            changed = False
            
            for item in inventory:
                mac = item["mac"].lower()
                is_online = False
                
                if item["id"] == "inv-simulated-1":
                    is_online = app_state["simulation_active"]
                else:
                    matched = devices_map.get(mac)
                    if matched and matched.get("status") != "Offline":
                        is_online = True
                        
                current_status = "Online" if is_online else "Offline"
                old_status = item.get("status", "Offline")
                
                if current_status != old_status:
                    item["status"] = current_status
                    changed = True
                    
                    now_str = time.strftime("%Y-%m-%d %H:%M:%S")
                    if is_online:
                        if "logs" not in item:
                            item["logs"] = []
                        item["logs"].append({
                            "time_on": now_str,
                            "time_off": None,
                            "duration": "Sedang Aktif"
                        })
                    else:
                        if "logs" in item and item["logs"]:
                            latest = item["logs"][-1]
                            if latest.get("time_off") is None:
                                latest["time_off"] = now_str
                                try:
                                    from datetime import datetime
                                    t1 = datetime.strptime(latest["time_on"], "%Y-%m-%d %H:%M:%S")
                                    t2 = datetime.strptime(now_str, "%Y-%m-%d %H:%M:%S")
                                    diff = t2 - t1
                                    hours, remainder = divmod(diff.seconds, 3600)
                                    minutes, _ = divmod(remainder, 60)
                                    latest["duration"] = f"{diff.days * 24 + hours} jam, {minutes} menit"
                                except Exception:
                                    latest["duration"] = "Selesai"
            if changed:
                save_inventory(inventory)
        except Exception as e:
            print(f"Error in inventory uptime loop: {e}")
        time.sleep(10.0)

threading.Thread(target=update_inventory_uptime_loop, daemon=True).start()

@app.route("/api/servers", methods=["GET"])
def get_servers_list():
    """Mengambil daftar ringkas server beserta status koneksinya."""
    servers_with_status = []
    for s in app_state["servers"]:
        srv_id = s["id"]
        # Ambil status koneksi cepat
        ctrl = server_controllers.get(srv_id)
        
        status_txt = "Offline"
        if s["type"] == "simulated":
            status_txt = "Online"
        elif s["type"] == "local":
            status_txt = "Online"
        elif ctrl and ctrl.is_connected:
            status_txt = "Online"
        else:
            # Lakukan test connection jika belum dicek
            if not ctrl:
                ctrl = ServerController(s["type"], s["ip"], s["username"], s["password"], s["os_type"])
                server_controllers[srv_id] = ctrl
            
            # Kita lakukan tes asinkron / timeout rendah
            is_ok, _ = ctrl.test_connection()
            status_txt = "Online" if is_ok else "Offline"

        servers_with_status.append({
            "id": srv_id,
            "name": s["name"],
            "ip": s["ip"],
            "type": s["type"],
            "os_type": s["os_type"],
            "status": status_txt
        })
    return jsonify(servers_with_status)

@app.route("/api/servers/add", methods=["POST"])
def add_new_server():
    """Menambahkan server baru ke daftar monitoring."""
    data = request.json
    name = data.get("name", "").strip()
    conn_type = data.get("type", "simulated")
    ip = data.get("ip", "").strip()
    username = data.get("username", "").strip()
    password = data.get("password", "")
    os_type = data.get("os_type", "linux")

    if not name:
        return jsonify({"success": False, "message": "Nama server harus diisi."}), 400
        
    if conn_type != "simulated" and conn_type != "local" and not ip:
        return jsonify({"success": False, "message": "Alamat IP harus diisi untuk koneksi jaringan."}), 400

    # Buat ID unik
    srv_id = "server-" + str(uuid.uuid4())[:8]

    # Inisialisasi kontroler baru untuk tes koneksi
    new_ctrl = ServerController(conn_type, ip, username, password, os_type)
    is_ok, test_msg = new_ctrl.test_connection()
    
    if not is_ok and conn_type != "simulated":
        # Jika bukan simulasi dan tes gagal, kembalikan error agar user tahu
        return jsonify({"success": False, "message": f"Koneksi Gagal: {test_msg}"}), 400

    # Masukkan ke state
    new_server = {
        "id": srv_id,
        "name": name,
        "type": conn_type,
        "ip": ip if conn_type not in ["simulated", "local"] else ("127.0.0.1" if conn_type == "local" else "192.168.1.50"),
        "username": username,
        "password": password,
        "os_type": os_type
    }
    app_state["servers"].append(new_server)
    server_controllers[srv_id] = new_ctrl
    save_servers(app_state["servers"])

    return jsonify({"success": True, "message": f"Server '{name}' berhasil ditambahkan.", "server": new_server})

@app.route("/api/servers/<server_id>", methods=["DELETE"])
def delete_server(server_id):
    """Menghapus server dari daftar monitoring."""
    if server_id in ["simulated-1", "local-host"]:
        return jsonify({"success": False, "message": "Server bawaan tidak dapat dihapus."}), 403

    # Cari server di list
    srv_to_remove = None
    for s in app_state["servers"]:
        if s["id"] == server_id:
            srv_to_remove = s
            break

    if srv_to_remove:
        app_state["servers"].remove(srv_to_remove)
        server_controllers.pop(server_id, None)
        save_servers(app_state["servers"])
        return jsonify({"success": True, "message": f"Server '{srv_to_remove['name']}' berhasil dihapus."})
    else:
        return jsonify({"success": False, "message": "Server tidak ditemukan."}), 404

@app.route("/api/servers/<server_id>/status", methods=["GET"])
def get_server_node_status(server_id):
    """Mengambil metrik utilisasi spesifik dari satu server dari cache."""
    stats = cached_server_metrics.get(server_id)
    if not stats:
        ctrl = server_controllers.get(server_id)
        if not ctrl:
            # Buat instansi lazy loading jika ada di konfigurasi
            srv_cfg = None
            for s in app_state["servers"]:
                if s["id"] == server_id:
                    srv_cfg = s
                    break
            if srv_cfg:
                ctrl = ServerController(srv_cfg["type"], srv_cfg["ip"], srv_cfg["username"], srv_cfg["password"], srv_cfg["os_type"])
                server_controllers[server_id] = ctrl
            else:
                return jsonify({"error": "Server tidak terdaftar."}), 404
                
        stats = ctrl.get_server_status()
        cached_server_metrics[server_id] = stats
    return jsonify(stats)

@app.route("/api/servers/<server_id>/command", methods=["POST"])
def run_server_node_command(server_id):
    """Mengeksekusi perintah terminal kustom pada satu server spesifik."""
    ctrl = server_controllers.get(server_id)
    if not ctrl:
        return jsonify({"success": False, "output": "Error: Server tidak aktif terhubung."}), 404

    data = request.json
    cmd = data.get("command", "")
    if not cmd:
        return jsonify({"success": False, "output": "Error: Perintah kosong."})

    output = ctrl.execute_command(cmd)
    return jsonify({"success": True, "output": output})


if __name__ == "__main__":
    print("Mengaktifkan LAN Guardian Web Server...")
    app.run(host="0.0.0.0", port=5000, debug=True, use_reloader=False)
