import os
import time

import psutil

# Module-level state for computing per-second I/O rates across samples
_last_disk_io = None
_last_net_io = None
_last_sample_time: float = None

# Processes we recognise as web application workers
_APP_WORKER_KEYWORDS = frozenset([
    'gunicorn', 'uvicorn', 'hypercorn', 'daphne',
    'node', 'nodejs',
    'puma', 'unicorn', 'ruby',
    'php-fpm',
])


def _count_app_workers() -> int:
    """
    Count recognised web-server worker processes running on the host.
    Best-effort: skips processes we cannot inspect without raising.
    """
    our_pid = os.getpid()
    count = 0
    try:
        for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
            try:
                if proc.info['pid'] == our_pid:
                    continue
                name = (proc.info['name'] or '').lower()
                cmdline = ' '.join(proc.info['cmdline'] or []).lower()
                if any(kw in name or kw in cmdline for kw in _APP_WORKER_KEYWORDS):
                    count += 1
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                continue
    except Exception:
        pass
    return count


def collect() -> dict:
    """
    Collect current system metrics.
    Returns a dict matching HeartbeatSerializer.metrics.
    Per-second I/O rates are computed as deltas from the previous sample.
    """
    global _last_disk_io, _last_net_io, _last_sample_time

    now = time.monotonic()
    elapsed = (now - _last_sample_time) if _last_sample_time else 0.0

    # Core CPU / Memory / Disk
    cpu = psutil.cpu_percent(interval=1)
    mem = psutil.virtual_memory()
    disk = psutil.disk_usage('/')
    swap = psutil.swap_memory()

    # Disk I/O - per-second rates via delta from last sample
    disk_read_bytes_s = 0
    disk_write_bytes_s = 0
    try:
        cur_disk = psutil.disk_io_counters()
        if cur_disk and _last_disk_io and elapsed > 0:
            disk_read_bytes_s = max(0, int(
                (cur_disk.read_bytes - _last_disk_io.read_bytes) / elapsed
            ))
            disk_write_bytes_s = max(0, int(
                (cur_disk.write_bytes - _last_disk_io.write_bytes) / elapsed
            ))
        _last_disk_io = cur_disk
    except Exception:
        pass

    # Network I/O - per-second rates via delta from last sample
    net_bytes_sent_s = 0.0
    net_bytes_recv_s = 0.0
    try:
        cur_net = psutil.net_io_counters()
        if cur_net and _last_net_io and elapsed > 0:
            net_bytes_sent_s = max(0.0,
                (cur_net.bytes_sent - _last_net_io.bytes_sent) / elapsed)
            net_bytes_recv_s = max(0.0,
                (cur_net.bytes_recv - _last_net_io.bytes_recv) / elapsed)
        _last_net_io = cur_net
    except Exception:
        pass

    _last_sample_time = now

    # Load averages - Unix only; None on Windows
    load_avg_1m = load_avg_5m = load_avg_15m = None
    try:
        load_avg_1m, load_avg_5m, load_avg_15m = os.getloadavg()
    except (AttributeError, OSError):
        pass

    # TCP connection state counts
    tcp_established = tcp_time_wait = tcp_close_wait = 0
    try:
        for conn in psutil.net_connections(kind='tcp'):
            s = conn.status
            if s == 'ESTABLISHED':
                tcp_established += 1
            elif s == 'TIME_WAIT':
                tcp_time_wait += 1
            elif s == 'CLOSE_WAIT':
                tcp_close_wait += 1
    except (psutil.AccessDenied, OSError):
        pass  # Needs elevated permissions on some systems

    return {
        'cpu_percent': cpu,
        'memory_percent': mem.percent,
        'memory_used_mb': round(mem.used / 1024 / 1024, 2),
        'memory_total_mb': round(mem.total / 1024 / 1024, 2),
        'disk_percent': disk.percent,
        'disk_read_bytes_s': disk_read_bytes_s,
        'disk_write_bytes_s': disk_write_bytes_s,
        'active_processes': len(psutil.pids()),
        'swap_used_mb': round(swap.used / 1024 / 1024, 2),
        'swap_total_mb': round(swap.total / 1024 / 1024, 2),
        'swap_percent': round(swap.percent, 1),
        'load_avg_1m': round(load_avg_1m, 2) if load_avg_1m is not None else None,
        'load_avg_5m': round(load_avg_5m, 2) if load_avg_5m is not None else None,
        'load_avg_15m': round(load_avg_15m, 2) if load_avg_15m is not None else None,
        'tcp_established': tcp_established,
        'tcp_time_wait': tcp_time_wait,
        'tcp_close_wait': tcp_close_wait,
        'net_bytes_sent_s': round(net_bytes_sent_s, 2),
        'net_bytes_recv_s': round(net_bytes_recv_s, 2),
        'app_worker_count': _count_app_workers(),
    }
