"""
CDS System Dashboard
Flask + Socket.IO realtime system monitoring dashboard.

Run:
    python app.py

Open:
    http://localhost:5013
"""

import os
import time

EVENTLET_AVAILABLE = False
try:
    import eventlet
    eventlet.monkey_patch()
    EVENTLET_AVAILABLE = True
except Exception:
    # Flask-SocketIO can still run with other async modes if eventlet is unavailable.
    pass

import psutil
from dotenv import load_dotenv
from flask import Flask, render_template
from flask_socketio import SocketIO

load_dotenv()

SECRET_KEY = os.getenv("SECRET_KEY", "cds-system-dashboard")
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", "5013"))
DEBUG = os.getenv("DEBUG", "false").strip().lower() in {"1", "true", "yes", "on"}
DISK_PATH = os.getenv("DISK_PATH", "/")
METRIC_INTERVAL = float(os.getenv("METRIC_INTERVAL", "2"))
ASYNC_MODE = "eventlet" if EVENTLET_AVAILABLE else "threading"

MONITORED_APPS = [
    item.strip()
    for item in os.getenv("MONITORED_APPS", "HAS.exe,LIS.exe,HIS.exe,mysqld,python,php-fpm").split(",")
    if item.strip()
]

app = Flask(__name__)
app.config["SECRET_KEY"] = SECRET_KEY

socketio = SocketIO(
    app,
    cors_allowed_origins="*",
    async_mode=ASYNC_MODE,
    ping_timeout=30,
    ping_interval=10,
)

last_net = psutil.net_io_counters()
last_disk = psutil.disk_io_counters()
last_time = time.time()
background_started = False


def bytes_to_gb(value):
    return round(float(value) / (1024 ** 3), 2)


def safe_disk_usage(path):
    try:
        usage = psutil.disk_usage(path)
        return {
            "path": path,
            "percent": round(usage.percent, 1),
            "used_gb": bytes_to_gb(usage.used),
            "free_gb": bytes_to_gb(usage.free),
            "capacity_gb": bytes_to_gb(usage.total),
        }
    except Exception:
        fallback = "C:\\" if os.name == "nt" else "/"
        usage = psutil.disk_usage(fallback)
        return {
            "path": fallback,
            "percent": round(usage.percent, 1),
            "used_gb": bytes_to_gb(usage.used),
            "free_gb": bytes_to_gb(usage.free),
            "capacity_gb": bytes_to_gb(usage.total),
        }


def iter_processes():
    rows = []

    for proc in psutil.process_iter(["pid", "name", "memory_percent"]):
        try:
            info = proc.info
            cpu_value = proc.cpu_percent(interval=None)
            rows.append({
                "pid": info.get("pid"),
                "name": info.get("name") or "unknown",
                "cpu": round(cpu_value or 0, 1),
                "memory": round(info.get("memory_percent") or 0, 1),
            })
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            continue
        except Exception:
            continue

    return rows


def get_top_processes(processes):
    top_cpu = sorted(processes, key=lambda x: x["cpu"], reverse=True)[:5]
    top_memory = sorted(processes, key=lambda x: x["memory"], reverse=True)[:5]
    return top_cpu, top_memory


def get_monitored_apps():
    result = []

    for app_name in MONITORED_APPS:
        instances = 0
        total_cpu = 0.0
        total_memory = 0.0
        total_clients = 0

        for proc in psutil.process_iter(["pid", "name", "memory_percent"]):
            try:
                proc_name = proc.info.get("name") or ""
                if proc_name.lower() != app_name.lower():
                    continue

                instances += 1
                total_cpu += proc.cpu_percent(interval=None) or 0
                total_memory += proc.info.get("memory_percent") or 0

                try:
                    conns = proc.net_connections(kind="inet")
                    total_clients += len([c for c in conns if c.status == psutil.CONN_ESTABLISHED])
                except Exception:
                    pass

            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                continue
            except Exception:
                continue

        result.append({
            "name": app_name,
            "status": "ONLINE" if instances > 0 else "OFFLINE",
            "instances": instances,
            "cpu": round(total_cpu, 1),
            "memory": round(total_memory, 1),
            "clients": total_clients,
        })

    return result


def collect_metrics():
    global last_net, last_disk, last_time

    now = time.time()
    elapsed = max(now - last_time, 1.0)

    cpu = psutil.cpu_percent(interval=None)
    memory = psutil.virtual_memory()

    net = psutil.net_io_counters()
    upload_bps = max(net.bytes_sent - last_net.bytes_sent, 0) / elapsed
    download_bps = max(net.bytes_recv - last_net.bytes_recv, 0) / elapsed

    disk_io = psutil.disk_io_counters()
    read_bps = 0.0
    write_bps = 0.0

    if disk_io and last_disk:
        read_bps = max(disk_io.read_bytes - last_disk.read_bytes, 0) / elapsed
        write_bps = max(disk_io.write_bytes - last_disk.write_bytes, 0) / elapsed

    processes = iter_processes()
    top_cpu, top_memory = get_top_processes(processes)

    last_net = net
    last_disk = disk_io
    last_time = now

    return {
        "timestamp": time.strftime("%I:%M:%S %p"),
        "cpu": round(cpu, 1),
        "memory": round(memory.percent, 1),
        "memory_detail": {
            "used_gb": bytes_to_gb(memory.used),
            "free_gb": bytes_to_gb(memory.available),
            "capacity_gb": bytes_to_gb(memory.total),
        },
        "network": {
            "upload_kbs": round(upload_bps / 1024, 2),
            "download_kbs": round(download_bps / 1024, 2),
            "upload_mbs": round(upload_bps / (1024 ** 2), 2),
            "download_mbs": round(download_bps / (1024 ** 2), 2),
        },
        "disk_io": {
            "read_mbs": round(read_bps / (1024 ** 2), 2),
            "write_mbs": round(write_bps / (1024 ** 2), 2),
        },
        "disk_storage": safe_disk_usage(DISK_PATH),
        "top_cpu": top_cpu,
        "top_memory": top_memory,
    }


def background_metrics():
    while True:
        socketio.emit("system_metrics", collect_metrics())
        socketio.sleep(METRIC_INTERVAL)


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


@socketio.on("connect")
def on_connect():
    global background_started

    if not background_started:
        background_started = True
        socketio.start_background_task(background_metrics)

    socketio.emit("system_metrics", collect_metrics())


if __name__ == "__main__":
    socketio.run(
        app,
        host=HOST,
        port=PORT,
        debug=DEBUG,
        use_reloader=False,
    )
