use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::OnceLock;
use std::time::Duration;

pub enum Stage {
    FrontendUnixRead,
    FrontendTcpRead,
    FrontendUnixToTcpWrite,
    FrontendTcpToUnixWrite,
    IoTcpRead,
    IoTcpWrite,
    IoCallback,
    RxqKick,
    GuestToTcpSend,
}

struct Counter {
    calls: AtomicU64,
    bytes: AtomicU64,
    us: AtomicU64,
}

impl Counter {
    const fn new() -> Self {
        Self {
            calls: AtomicU64::new(0),
            bytes: AtomicU64::new(0),
            us: AtomicU64::new(0),
        }
    }
}

static FRONTEND_UNIX_READ: Counter = Counter::new();
static FRONTEND_TCP_READ: Counter = Counter::new();
static FRONTEND_UNIX_TO_TCP_WRITE: Counter = Counter::new();
static FRONTEND_TCP_TO_UNIX_WRITE: Counter = Counter::new();
static IO_TCP_READ: Counter = Counter::new();
static IO_TCP_WRITE: Counter = Counter::new();
static IO_CALLBACK: Counter = Counter::new();
static RXQ_KICK: Counter = Counter::new();
static GUEST_TO_TCP_SEND: Counter = Counter::new();

pub fn enabled() -> bool {
    static ENABLED: OnceLock<bool> = OnceLock::new();
    *ENABLED.get_or_init(|| {
        let on = matches!(
            std::env::var("SUPERMACHINE_MUX_PROFILE").as_deref(),
            Ok("1") | Ok("true") | Ok("yes") | Ok("on")
        );
        if on {
            start_reporter();
        }
        on
    })
}

pub fn record(stage: Stage, bytes: usize, us: u64) {
    if !enabled() {
        return;
    }
    let c = counter(stage);
    c.calls.fetch_add(1, Ordering::Relaxed);
    c.bytes.fetch_add(bytes as u64, Ordering::Relaxed);
    c.us.fetch_add(us, Ordering::Relaxed);
}

fn counter(stage: Stage) -> &'static Counter {
    match stage {
        Stage::FrontendUnixRead => &FRONTEND_UNIX_READ,
        Stage::FrontendTcpRead => &FRONTEND_TCP_READ,
        Stage::FrontendUnixToTcpWrite => &FRONTEND_UNIX_TO_TCP_WRITE,
        Stage::FrontendTcpToUnixWrite => &FRONTEND_TCP_TO_UNIX_WRITE,
        Stage::IoTcpRead => &IO_TCP_READ,
        Stage::IoTcpWrite => &IO_TCP_WRITE,
        Stage::IoCallback => &IO_CALLBACK,
        Stage::RxqKick => &RXQ_KICK,
        Stage::GuestToTcpSend => &GUEST_TO_TCP_SEND,
    }
}

fn start_reporter() {
    static STARTED: OnceLock<()> = OnceLock::new();
    STARTED.get_or_init(|| {
        std::thread::Builder::new()
            .name("mux-profile".into())
            .spawn(|| {
                let mut prev = snapshot();
                loop {
                    std::thread::sleep(Duration::from_secs(1));
                    let now = snapshot();
                    eprintln!("[mux-profile] {}", format_delta(&prev, &now));
                    prev = now;
                }
            })
            .ok();
    });
}

#[derive(Clone, Copy)]
struct Snap {
    calls: [u64; 9],
    bytes: [u64; 9],
    us: [u64; 9],
}

fn snapshot() -> Snap {
    let counters = [
        &FRONTEND_UNIX_READ,
        &FRONTEND_TCP_READ,
        &FRONTEND_UNIX_TO_TCP_WRITE,
        &FRONTEND_TCP_TO_UNIX_WRITE,
        &IO_TCP_READ,
        &IO_TCP_WRITE,
        &IO_CALLBACK,
        &RXQ_KICK,
        &GUEST_TO_TCP_SEND,
    ];
    let mut calls = [0; 9];
    let mut bytes = [0; 9];
    let mut us = [0; 9];
    for (i, c) in counters.iter().enumerate() {
        calls[i] = c.calls.load(Ordering::Relaxed);
        bytes[i] = c.bytes.load(Ordering::Relaxed);
        us[i] = c.us.load(Ordering::Relaxed);
    }
    Snap { calls, bytes, us }
}

fn format_delta(prev: &Snap, now: &Snap) -> String {
    const NAMES: [&str; 9] = [
        "front_unix_read",
        "front_tcp_read",
        "front_u2t_write",
        "front_t2u_write",
        "io_tcp_read",
        "io_tcp_write",
        "io_callback",
        "rxq_kick",
        "guest_to_tcp_send",
    ];
    let mut out = String::new();
    for i in 0..NAMES.len() {
        let calls = now.calls[i].saturating_sub(prev.calls[i]);
        if calls == 0 {
            continue;
        }
        let bytes = now.bytes[i].saturating_sub(prev.bytes[i]);
        let us = now.us[i].saturating_sub(prev.us[i]);
        if !out.is_empty() {
            out.push_str(" | ");
        }
        out.push_str(&format!(
            "{} calls={} bytes={} avg_us={:.2}",
            NAMES[i],
            calls,
            bytes,
            us as f64 / calls as f64
        ));
    }
    if out.is_empty() {
        "idle".to_owned()
    } else {
        out
    }
}
