//! Logger daemon (IPC receiver + async file writer)

use crate::errors::{FalcornError, Result};
use crate::logging::ipc::{
    LogEvent, PLUGIN_FILTER_ALL, PLUGIN_FRAME_HEADER_LEN, PLUGIN_FRAME_MAGIC,
    PLUGIN_HEARTBEAT_INTERVAL_SECS, PLUGIN_HEARTBEAT_TIMEOUT_SECS, PLUGIN_MAX_PAYLOAD_LEN,
    PLUGIN_PROTOCOL_VERSION, PluginErrorFrame, PluginEvent, PluginFrameType, PluginMetricsEvent,
    PluginPingFrame, PluginPongFrame, PluginSubscribeAck, PluginSubscribeRequest,
};
use crate::logging::metrics::Metrics;
use chrono::Local;
use std::collections::{HashMap, VecDeque};
use std::os::fd::AsRawFd;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use tokio::fs::OpenOptions;
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufWriter};
use tokio::net::UnixListener;
use tokio::sync::{Semaphore, broadcast, mpsc};
use tokio::time::{Duration, Instant, interval, timeout};

#[derive(Debug, Clone, Copy)]
enum PluginDropPolicy {
    DropOldest,
    Disconnect,
}

#[derive(Debug, Clone, Copy)]
enum PluginAclMode {
    Off,
    SameUser,
}

#[derive(Debug, Clone, Copy)]
struct PluginBackpressureSettings {
    drop_policy: PluginDropPolicy,
    max_lagged_events: usize,
    write_timeout: Duration,
    bus_capacity: usize,
    max_clients: usize,
    subscribe_timeout: Duration,
    auth_fail_window: Duration,
    auth_fail_max_attempts: usize,
    acl_mode: PluginAclMode,
    socket_mode: u32,
}

#[derive(Default)]
struct PluginIpcStats {
    active_clients: AtomicUsize,
    accepted_connections_total: AtomicU64,
    acl_denied_total: AtomicU64,
    auth_failed_total: AtomicU64,
    auth_rate_limited_total: AtomicU64,
    subscribe_invalid_total: AtomicU64,
    subscribe_timeout_total: AtomicU64,
    slow_consumer_disconnect_total: AtomicU64,
    heartbeat_timeout_total: AtomicU64,
    write_timeout_total: AtomicU64,
}

impl PluginIpcStats {
    fn snapshot_line(&self) -> String {
        format!(
            "PLUGIN_IPC active_clients={} accepted_total={} acl_denied_total={} auth_failed_total={} auth_rate_limited_total={} subscribe_invalid_total={} subscribe_timeout_total={} slow_consumer_disconnect_total={} heartbeat_timeout_total={} write_timeout_total={}",
            self.active_clients.load(Ordering::Relaxed),
            self.accepted_connections_total.load(Ordering::Relaxed),
            self.acl_denied_total.load(Ordering::Relaxed),
            self.auth_failed_total.load(Ordering::Relaxed),
            self.auth_rate_limited_total.load(Ordering::Relaxed),
            self.subscribe_invalid_total.load(Ordering::Relaxed),
            self.subscribe_timeout_total.load(Ordering::Relaxed),
            self.slow_consumer_disconnect_total.load(Ordering::Relaxed),
            self.heartbeat_timeout_total.load(Ordering::Relaxed),
            self.write_timeout_total.load(Ordering::Relaxed),
        )
    }
}

struct PluginBus {
    sender: broadcast::Sender<PluginEvent>,
    stats: Arc<PluginIpcStats>,
}

pub async fn run_logger_daemon(
    socket_path: &str,
    access_log: Option<String>,
    error_log: Option<String>,
    access_log_format: String,
    log_stdout: bool,
    plugin_socket: Option<String>,
    plugin_auth_token: Option<String>,
    plugin_drop_policy: String,
    plugin_max_lagged_events: usize,
    plugin_write_timeout_ms: u64,
    plugin_bus_capacity: usize,
    plugin_max_clients: usize,
    plugin_subscribe_timeout_ms: u64,
    plugin_auth_fail_window_secs: u64,
    plugin_auth_fail_max_attempts: usize,
    plugin_acl_mode: String,
    plugin_socket_mode: u32,
    metrics_interval_ms: u64,
    metrics_workers_enabled: bool,
    metrics_os_enabled: bool,
    metrics_percentiles_enabled: bool,
    metrics_top_errors_limit: usize,
) -> Result<()> {
    // Remove stale socket if exists
    let _ = std::fs::remove_file(socket_path);

    let listener = UnixListener::bind(socket_path)
        .map_err(|e| FalcornError::runtime(format!("Logger bind error: {}", e)))?;

    let plugin_settings = PluginBackpressureSettings {
        drop_policy: parse_plugin_drop_policy(&plugin_drop_policy)?,
        max_lagged_events: plugin_max_lagged_events.max(1),
        write_timeout: Duration::from_millis(plugin_write_timeout_ms.max(1)),
        bus_capacity: plugin_bus_capacity.max(1),
        max_clients: plugin_max_clients.max(1),
        subscribe_timeout: Duration::from_millis(plugin_subscribe_timeout_ms.max(1)),
        auth_fail_window: Duration::from_secs(plugin_auth_fail_window_secs.max(1)),
        auth_fail_max_attempts: plugin_auth_fail_max_attempts.max(1),
        acl_mode: parse_plugin_acl_mode(&plugin_acl_mode)?,
        socket_mode: plugin_socket_mode,
    };

    let (tx, mut rx) = mpsc::channel::<LogEvent>(8192);
    let plugin_bus = if let Some(path) = plugin_socket.as_deref() {
        let bus = setup_plugin_ipc(path, plugin_auth_token, plugin_settings).await?;
        Some(bus)
    } else {
        None
    };

    // Writer task
    let access_path = access_log.clone();
    let error_path = error_log.clone();
    tokio::spawn(async move {
        let mut access_writer = open_writer(access_path.as_deref()).await;
        let mut error_writer = open_writer(error_path.as_deref()).await;
        let metrics = Metrics::new(metrics_percentiles_enabled);
        let mut last_metrics_tick = Instant::now();
        let metrics_interval = Duration::from_millis(metrics_interval_ms.max(1));
        let mut cpu_tracker = CpuTracker::new();

        let mut access_buf = String::new();
        let mut error_buf = String::new();
        let mut ticker = interval(Duration::from_millis(200));

        loop {
            tokio::select! {
                Some(evt) = rx.recv() => {
                    match evt {
                        LogEvent::Message { level, ts_millis, pid, worker_id, msg } => {
                            let ts = format_ts(ts_millis);
                            let wid = worker_id.map(|w| format!(" worker={}", w)).unwrap_or_default();
                            let line = format!("[{}] {} pid={}{} - {}\n", ts, level.as_str(), pid, wid, msg);
                            if level == crate::config::LogLevel::Error {
                                error_buf.push_str(&line);
                            } else {
                                access_buf.push_str(&line);
                            }
                            if log_stdout {
                                print!("{}", line);
                            }
                            if let Some(bus) = plugin_bus.as_ref() {
                                let _ = bus.sender.send(PluginEvent::LogMessage {
                                    ts_millis,
                                    level,
                                    pid,
                                    worker_id,
                                    message: msg,
                                });
                            }
                        }
                        LogEvent::Access { ts_millis, pid, worker_id, remote, method, uri, version, status, size, duration_micros } => {
                            let ts = format_ts(ts_millis);
                            let line = format_access_log(
                                &access_log_format,
                                &remote,
                                &method,
                                &uri,
                                &version,
                                status,
                                size,
                                &ts,
                            );
                            access_buf.push_str(&line);
                            access_buf.push('\n');
                            metrics.record_request(
                                status,
                                Duration::from_micros(duration_micros),
                                worker_id,
                                Some(pid as i32),
                            );
                            if log_stdout {
                                println!("{}", line);
                            }
                            if let Some(bus) = plugin_bus.as_ref() {
                                let _ = bus.sender.send(PluginEvent::AccessLog {
                                    ts_millis,
                                    remote,
                                    method,
                                    uri,
                                    version,
                                    status,
                                    size,
                                    duration_micros,
                                });
                            }
                        }
                        LogEvent::WorkerStats {
                            ts_millis: _,
                            pid,
                            worker_id,
                            active_connections,
                            total_requests,
                        } => {
                            if metrics_workers_enabled {
                                metrics.record_worker_stats(
                                    worker_id,
                                    pid,
                                    active_connections,
                                    total_requests,
                                );
                            }
                        }
                    }
                    if access_buf.len() > 8 * 1024 {
                        flush_buf(&mut access_writer, &mut access_buf).await;
                    }
                    if error_buf.len() > 8 * 1024 {
                        flush_buf(&mut error_writer, &mut error_buf).await;
                    }
                }
                _ = ticker.tick() => {
                    if last_metrics_tick.elapsed() >= metrics_interval {
                        let snapshot = metrics.snapshot();
                        if snapshot.total_requests > 0 {
                            let mut pid_stats: HashMap<i32, (Option<f64>, Option<u64>)> = HashMap::new();
                            let mut total_cpu = 0.0;
                            let mut total_mem = 0u64;
                            if metrics_os_enabled && !snapshot.workers.is_empty() {
                                let total_jiffies = cpu_tracker.read_total_jiffies();
                                for worker in &snapshot.workers {
                                    if let Some(pid) = worker.pid {
                                        let cpu = cpu_tracker.read_pid_cpu_percent(pid, total_jiffies);
                                        let mem = read_pid_mem_bytes(pid);
                                        if let Some(cpu_val) = cpu {
                                            total_cpu += cpu_val;
                                        }
                                        if let Some(mem_val) = mem {
                                            total_mem = total_mem.saturating_add(mem_val);
                                        }
                                        pid_stats.insert(pid, (cpu, mem));
                                    }
                                }
                                cpu_tracker.commit(total_jiffies);
                            }
                            let instance_cpu = if total_cpu > 0.0 { Some(total_cpu) } else { None };
                            let instance_mem = if total_mem > 0 { Some(total_mem) } else { None };

                            let line = format_metrics_line(&snapshot);
                            error_buf.push_str(&line);
                            error_buf.push('\n');
                            if log_stdout {
                                println!("{}", line);
                            }
                            if let Some(bus) = plugin_bus.as_ref() {
                                let workers = if snapshot.workers.is_empty() {
                                    None
                                } else {
                                    Some(
                                        snapshot
                                            .workers
                                            .iter()
                                            .map(|worker| falcorn_proto::logging::WorkerMetrics {
                                                id: worker.id,
                                                pid: worker.pid,
                                                active: worker.active,
                                                restarts: worker.restarts,
                                                uptime_secs: worker.uptime.as_secs(),
                                                req_total: worker.total_requests,
                                                rps: worker.rps,
                                                success_rate: worker.success_rate,
                                                error_rate: worker.error_rate,
                                                active_connections: worker.active_connections,
                                                cpu_percent: worker
                                                    .pid
                                                    .and_then(|pid| pid_stats.get(&pid).and_then(|v| v.0)),
                                                mem_bytes: worker
                                                    .pid
                                                    .and_then(|pid| pid_stats.get(&pid).and_then(|v| v.1)),
                                            })
                                            .collect(),
                                    )
                                };
                                let top_errors = {
                                    let mut items = Vec::new();
                                    if snapshot.status_4xx > 0 {
                                        items.push(falcorn_proto::logging::ErrorStat {
                                            code: "4xx".to_string(),
                                            count: snapshot.status_4xx,
                                        });
                                    }
                                    if snapshot.status_5xx > 0 {
                                        items.push(falcorn_proto::logging::ErrorStat {
                                            code: "5xx".to_string(),
                                            count: snapshot.status_5xx,
                                        });
                                    }
                                    if metrics_top_errors_limit == 0 {
                                        None
                                    } else if items.is_empty() {
                                        None
                                    } else {
                                        items.truncate(metrics_top_errors_limit);
                                        Some(items)
                                    }
                                };
                                let _ = bus.sender.send(PluginEvent::Metrics(PluginMetricsEvent {
                                    uptime_secs: snapshot.uptime.as_secs(),
                                    total_requests: snapshot.total_requests,
                                    successful_requests: snapshot.successful_requests,
                                    failed_requests: snapshot.failed_requests,
                                    requests_per_second: snapshot.requests_per_second(),
                                    success_rate: snapshot.success_rate(),
                                    failure_rate: snapshot.failure_rate(),
                                    avg_response_time_micros: snapshot.avg_response_time.as_micros().min(u128::from(u64::MAX)) as u64,
                                    min_response_time_micros: snapshot.min_response_time.as_micros().min(u128::from(u64::MAX)) as u64,
                                    max_response_time_micros: snapshot.max_response_time.as_micros().min(u128::from(u64::MAX)) as u64,
                                    p50_response_time_micros: snapshot.p50_response_time_micros,
                                    p95_response_time_micros: snapshot.p95_response_time_micros,
                                    p99_response_time_micros: snapshot.p99_response_time_micros,
                                    p999_response_time_micros: snapshot.p999_response_time_micros,
                                    active_connections: snapshot.active_connections,
                                    total_connections: snapshot.total_connections,
                                    worker_restarts: snapshot.worker_restarts,
                                    status_2xx: snapshot.status_2xx,
                                    status_3xx: snapshot.status_3xx,
                                    status_4xx: snapshot.status_4xx,
                                    status_5xx: snapshot.status_5xx,
                                    backlog: None,
                                    cpu_percent: instance_cpu,
                                    mem_bytes: instance_mem,
                                    error_rate: Some(snapshot.failure_rate()),
                                    error_4xx: Some(snapshot.status_4xx),
                                    error_5xx: Some(snapshot.status_5xx),
                                    top_errors,
                                    workers: if metrics_workers_enabled { workers } else { None },
                                }));
                            }
                        }
                        if let Some(bus) = plugin_bus.as_ref() {
                            let stats_line = bus.stats.snapshot_line();
                            error_buf.push_str(&stats_line);
                            error_buf.push('\n');
                            if log_stdout {
                                println!("{}", stats_line);
                            }
                        }
                        last_metrics_tick = Instant::now();
                    }
                    flush_buf(&mut access_writer, &mut access_buf).await;
                    flush_buf(&mut error_writer, &mut error_buf).await;
                }
            }
        }
    });

    loop {
        let (mut socket, _) = listener
            .accept()
            .await
            .map_err(|e| FalcornError::runtime(format!("Logger accept error: {}", e)))?;
        let tx = tx.clone();

        tokio::spawn(async move {
            loop {
                let mut len_buf = [0u8; 4];
                if socket.read_exact(&mut len_buf).await.is_err() {
                    break;
                }
                let len = u32::from_le_bytes(len_buf) as usize;
                let mut payload = vec![0u8; len];
                if socket.read_exact(&mut payload).await.is_err() {
                    break;
                }
                if let Ok(evt) = bincode::deserialize::<LogEvent>(&payload) {
                    let _ = tx.send(evt).await;
                }
            }
        });
    }
}

async fn setup_plugin_ipc(
    path: &str,
    plugin_auth_token: Option<String>,
    settings: PluginBackpressureSettings,
) -> Result<PluginBus> {
    let _ = std::fs::remove_file(path);
    let listener = UnixListener::bind(path)
        .map_err(|e| FalcornError::runtime(format!("Plugin IPC bind error: {}", e)))?;
    set_plugin_socket_permissions(path, settings.socket_mode)?;

    let (tx, _) = broadcast::channel::<PluginEvent>(settings.bus_capacity);
    let accept_tx = tx.clone();
    let connection_limiter = Arc::new(Semaphore::new(settings.max_clients));
    let auth_failures = Arc::new(Mutex::new(HashMap::<u32, VecDeque<Instant>>::new()));
    let stats = Arc::new(PluginIpcStats::default());
    let accept_stats = stats.clone();

    tokio::spawn(async move {
        while let Ok((mut socket, _)) = listener.accept().await {
            accept_stats
                .accepted_connections_total
                .fetch_add(1, Ordering::Relaxed);
            let permit = match connection_limiter.clone().acquire_owned().await {
                Ok(permit) => permit,
                Err(_) => break,
            };
            let mut rx = accept_tx.subscribe();
            let expected_token = plugin_auth_token.clone();
            let settings = settings;
            let auth_failures = auth_failures.clone();
            let stats = accept_stats.clone();
            tokio::spawn(async move {
                let _permit = permit;
                let _active_guard = ActiveClientGuard::new(stats.clone());
                let peer_uid = peer_euid_or_zero(&socket);

                if is_auth_rate_limited(&auth_failures, peer_uid, &settings) {
                    stats
                        .auth_rate_limited_total
                        .fetch_add(1, Ordering::Relaxed);
                    let _ = write_error_frame_timed(
                        &mut socket,
                        "auth_rate_limited",
                        "Too many failed authentication attempts; retry later",
                        settings.write_timeout,
                    )
                    .await;
                    return;
                }

                if let Err(e) = enforce_peer_acl(&socket, settings.acl_mode) {
                    stats.acl_denied_total.fetch_add(1, Ordering::Relaxed);
                    let _ = write_error_frame_timed(
                        &mut socket,
                        "acl_denied",
                        &e.to_string(),
                        settings.write_timeout,
                    )
                    .await;
                    return;
                }

                let subscribe =
                    match read_subscription(&mut socket, settings.subscribe_timeout).await {
                        Ok(req) => req,
                        Err(e) => {
                            if e.to_string().contains("Plugin subscribe timeout") {
                                stats
                                    .subscribe_timeout_total
                                    .fetch_add(1, Ordering::Relaxed);
                            } else {
                                stats
                                    .subscribe_invalid_total
                                    .fetch_add(1, Ordering::Relaxed);
                            }
                            let _ = write_error_frame_timed(
                                &mut socket,
                                "invalid_subscription",
                                &e.to_string(),
                                settings.write_timeout,
                            )
                            .await;
                            return;
                        }
                    };

                if let Err(e) = authenticate_subscription(&subscribe, expected_token.as_deref()) {
                    stats.auth_failed_total.fetch_add(1, Ordering::Relaxed);
                    record_auth_failure(&auth_failures, peer_uid, &settings);
                    let _ = write_error_frame_timed(
                        &mut socket,
                        "authentication_failed",
                        &e.to_string(),
                        settings.write_timeout,
                    )
                    .await;
                    return;
                }
                clear_auth_failures(&auth_failures, peer_uid);

                let filter_mask = normalize_filter_mask(subscribe.filter_mask);
                let (mut rd, mut wr) = socket.into_split();

                let ack = PluginSubscribeAck {
                    protocol_version: PLUGIN_PROTOCOL_VERSION,
                    accepted_filter_mask: filter_mask,
                    heartbeat_interval_secs: PLUGIN_HEARTBEAT_INTERVAL_SECS,
                    heartbeat_timeout_secs: PLUGIN_HEARTBEAT_TIMEOUT_SECS,
                };
                if write_typed_frame_timed(
                    &mut wr,
                    PluginFrameType::Ack,
                    &ack,
                    settings.write_timeout,
                )
                .await
                .is_err()
                {
                    return;
                }

                let mut heartbeat = interval(Duration::from_secs(PLUGIN_HEARTBEAT_INTERVAL_SECS));
                let mut last_pong = Instant::now();
                let mut lagged_burst: usize = 0;

                loop {
                    tokio::select! {
                        incoming = read_frame(&mut rd) => {
                            let (frame_type, payload) = match incoming {
                                Ok(v) => v,
                                Err(_) => break,
                            };

                            match frame_type {
                                PluginFrameType::Pong => {
                                    if bincode::deserialize::<PluginPongFrame>(&payload).is_ok() {
                                        last_pong = Instant::now();
                                    } else {
                                        let _ = write_error_frame_timed(&mut wr, "invalid_pong", "Invalid pong payload", settings.write_timeout).await;
                                        break;
                                    }
                                }
                                PluginFrameType::Ping => {
                                    let ping = match bincode::deserialize::<PluginPingFrame>(&payload) {
                                        Ok(p) => p,
                                        Err(_) => {
                                            let _ = write_error_frame_timed(&mut wr, "invalid_ping", "Invalid ping payload", settings.write_timeout).await;
                                            break;
                                        }
                                    };
                                    let pong = PluginPongFrame { ts_millis: ping.ts_millis };
                                    if write_typed_frame_timed(&mut wr, PluginFrameType::Pong, &pong, settings.write_timeout).await.is_err() {
                                        break;
                                    }
                                }
                                _ => {
                                    let _ = write_error_frame_timed(&mut wr, "unexpected_frame", "Only ping/pong frames are accepted after subscribe", settings.write_timeout).await;
                                    break;
                                }
                            }
                        }
                        evt = rx.recv() => {
                            match evt {
                                Ok(event) => {
                                    lagged_burst = 0;
                                    if event.filter_bit() & filter_mask == 0 {
                                        continue;
                                    }
                                    if write_typed_frame_timed(&mut wr, PluginFrameType::Event, &event, settings.write_timeout).await.is_err() {
                                        break;
                                    }
                                }
                                Err(broadcast::error::RecvError::Lagged(skipped)) => {
                                    lagged_burst = lagged_burst.saturating_add(skipped as usize);

                                    let should_disconnect = match settings.drop_policy {
                                        PluginDropPolicy::Disconnect => true,
                                        PluginDropPolicy::DropOldest => lagged_burst > settings.max_lagged_events,
                                    };

                                    if should_disconnect {
                                        stats
                                            .slow_consumer_disconnect_total
                                            .fetch_add(1, Ordering::Relaxed);
                                        let _ = write_error_frame_timed(
                                            &mut wr,
                                            "slow_consumer",
                                            "Plugin client lagged too much behind the event stream",
                                            settings.write_timeout,
                                        ).await;
                                        break;
                                    }
                                    continue;
                                }
                                Err(broadcast::error::RecvError::Closed) => break,
                            }
                        }
                        _ = heartbeat.tick() => {
                            if last_pong.elapsed() > Duration::from_secs(PLUGIN_HEARTBEAT_TIMEOUT_SECS) {
                                stats
                                    .heartbeat_timeout_total
                                    .fetch_add(1, Ordering::Relaxed);
                                let _ = write_error_frame_timed(&mut wr, "heartbeat_timeout", "Plugin client did not respond to ping", settings.write_timeout).await;
                                break;
                            }
                            let ping = PluginPingFrame { ts_millis: now_millis() };
                            if write_typed_frame_timed(&mut wr, PluginFrameType::Ping, &ping, settings.write_timeout).await.is_err() {
                                stats.write_timeout_total.fetch_add(1, Ordering::Relaxed);
                                break;
                            }
                        }
                    }
                }
            });
        }
    });

    Ok(PluginBus { sender: tx, stats })
}

async fn read_subscription<S: AsyncReadExt + Unpin>(
    stream: &mut S,
    subscribe_timeout: Duration,
) -> Result<PluginSubscribeRequest> {
    let (frame_type, payload) = timeout(subscribe_timeout, read_frame(stream))
        .await
        .map_err(|_| FalcornError::runtime("Plugin subscribe timeout"))??;

    if frame_type != PluginFrameType::Subscribe {
        return Err(FalcornError::runtime(
            "Expected subscribe frame as first plugin message",
        ));
    }

    bincode::deserialize(&payload)
        .map_err(|e| FalcornError::runtime(format!("Invalid subscribe payload: {}", e)))
}

fn authenticate_subscription(
    subscribe: &PluginSubscribeRequest,
    expected_token: Option<&str>,
) -> Result<()> {
    match expected_token {
        Some(expected) => {
            let provided = subscribe.auth_token.as_deref().ok_or_else(|| {
                FalcornError::runtime("Missing auth token in plugin subscribe request")
            })?;
            if provided == expected {
                Ok(())
            } else {
                Err(FalcornError::runtime("Invalid plugin auth token"))
            }
        }
        None => Ok(()),
    }
}

fn normalize_filter_mask(mask: u8) -> u8 {
    let masked = mask & PLUGIN_FILTER_ALL;
    if masked == 0 {
        PLUGIN_FILTER_ALL
    } else {
        masked
    }
}

async fn write_error_frame_timed<S: AsyncWriteExt + Unpin>(
    stream: &mut S,
    code: &str,
    message: &str,
    timeout_duration: Duration,
) -> Result<()> {
    let payload = PluginErrorFrame {
        code: code.to_string(),
        message: message.to_string(),
    };
    write_typed_frame_timed(stream, PluginFrameType::Error, &payload, timeout_duration).await
}

async fn write_typed_frame<S: AsyncWriteExt + Unpin, T: serde::Serialize>(
    stream: &mut S,
    frame_type: PluginFrameType,
    payload: &T,
) -> Result<()> {
    let body = bincode::serialize(payload)
        .map_err(|e| FalcornError::runtime(format!("Plugin serialize error: {}", e)))?;
    if body.len() > PLUGIN_MAX_PAYLOAD_LEN {
        return Err(FalcornError::runtime("Plugin payload too large"));
    }

    let mut header = [0u8; PLUGIN_FRAME_HEADER_LEN];
    header[0..4].copy_from_slice(&PLUGIN_FRAME_MAGIC);
    header[4] = PLUGIN_PROTOCOL_VERSION;
    header[5] = frame_type as u8;
    header[6..8].copy_from_slice(&0u16.to_le_bytes()); // reserved flags
    header[8..12].copy_from_slice(&(body.len() as u32).to_le_bytes());

    stream
        .write_all(&header)
        .await
        .map_err(FalcornError::IoError)?;
    stream
        .write_all(&body)
        .await
        .map_err(FalcornError::IoError)?;
    Ok(())
}

async fn write_typed_frame_timed<S: AsyncWriteExt + Unpin, T: serde::Serialize>(
    stream: &mut S,
    frame_type: PluginFrameType,
    payload: &T,
    timeout_duration: Duration,
) -> Result<()> {
    timeout(
        timeout_duration,
        write_typed_frame(stream, frame_type, payload),
    )
    .await
    .map_err(|_| FalcornError::runtime("Plugin write timeout"))?
}

async fn read_frame<S: AsyncReadExt + Unpin>(stream: &mut S) -> Result<(PluginFrameType, Vec<u8>)> {
    let mut header = [0u8; PLUGIN_FRAME_HEADER_LEN];
    stream
        .read_exact(&mut header)
        .await
        .map_err(FalcornError::IoError)?;

    if header[0..4] != PLUGIN_FRAME_MAGIC {
        return Err(FalcornError::runtime("Invalid plugin frame magic"));
    }
    if header[4] != PLUGIN_PROTOCOL_VERSION {
        return Err(FalcornError::runtime("Unsupported plugin protocol version"));
    }

    let frame_type = PluginFrameType::from_u8(header[5])
        .ok_or_else(|| FalcornError::runtime("Unknown plugin frame type"))?;
    let len = u32::from_le_bytes([header[8], header[9], header[10], header[11]]) as usize;

    if len > PLUGIN_MAX_PAYLOAD_LEN {
        return Err(FalcornError::runtime("Plugin frame payload too large"));
    }

    let mut payload = vec![0u8; len];
    stream
        .read_exact(&mut payload)
        .await
        .map_err(FalcornError::IoError)?;

    Ok((frame_type, payload))
}

fn now_millis() -> u64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as u64
}

fn parse_plugin_drop_policy(policy: &str) -> Result<PluginDropPolicy> {
    match policy.to_lowercase().as_str() {
        "drop_oldest" => Ok(PluginDropPolicy::DropOldest),
        "disconnect" => Ok(PluginDropPolicy::Disconnect),
        other => Err(FalcornError::runtime(format!(
            "Invalid plugin_drop_policy '{}': expected 'drop_oldest' or 'disconnect'",
            other
        ))),
    }
}

fn parse_plugin_acl_mode(mode: &str) -> Result<PluginAclMode> {
    match mode.to_lowercase().as_str() {
        "off" => Ok(PluginAclMode::Off),
        "same_user" => Ok(PluginAclMode::SameUser),
        other => Err(FalcornError::runtime(format!(
            "Invalid plugin_acl_mode '{}': expected 'off' or 'same_user'",
            other
        ))),
    }
}

fn set_plugin_socket_permissions(path: &str, mode: u32) -> Result<()> {
    use std::os::unix::fs::PermissionsExt;

    let perms = std::fs::Permissions::from_mode(mode & 0o777);
    std::fs::set_permissions(path, perms).map_err(|e| {
        FalcornError::runtime(format!(
            "Failed to set plugin socket permissions ({:o}) on {}: {}",
            mode, path, e
        ))
    })
}

fn enforce_peer_acl(stream: &tokio::net::UnixStream, acl_mode: PluginAclMode) -> Result<()> {
    match acl_mode {
        PluginAclMode::Off => Ok(()),
        PluginAclMode::SameUser => {
            let peer_uid = peer_euid(stream)?;
            let local_uid = unsafe { libc::geteuid() as u32 };
            if peer_uid == local_uid {
                Ok(())
            } else {
                Err(FalcornError::runtime(format!(
                    "ACL denied: peer uid {} does not match server uid {}",
                    peer_uid, local_uid
                )))
            }
        }
    }
}

fn peer_euid_or_zero(stream: &tokio::net::UnixStream) -> u32 {
    peer_euid(stream).unwrap_or(0)
}

fn is_auth_rate_limited(
    failures: &Arc<Mutex<HashMap<u32, VecDeque<Instant>>>>,
    uid: u32,
    settings: &PluginBackpressureSettings,
) -> bool {
    let mut guard = failures.lock().expect("auth failures mutex poisoned");
    let attempts = guard.entry(uid).or_default();
    prune_attempts(attempts, settings.auth_fail_window);
    attempts.len() >= settings.auth_fail_max_attempts
}

fn record_auth_failure(
    failures: &Arc<Mutex<HashMap<u32, VecDeque<Instant>>>>,
    uid: u32,
    settings: &PluginBackpressureSettings,
) {
    let mut guard = failures.lock().expect("auth failures mutex poisoned");
    let attempts = guard.entry(uid).or_default();
    prune_attempts(attempts, settings.auth_fail_window);
    attempts.push_back(Instant::now());
}

fn clear_auth_failures(failures: &Arc<Mutex<HashMap<u32, VecDeque<Instant>>>>, uid: u32) {
    let mut guard = failures.lock().expect("auth failures mutex poisoned");
    guard.remove(&uid);
}

fn prune_attempts(attempts: &mut VecDeque<Instant>, window: Duration) {
    let now = Instant::now();
    while let Some(front) = attempts.front() {
        if now.duration_since(*front) <= window {
            break;
        }
        attempts.pop_front();
    }
}

struct ActiveClientGuard {
    stats: Arc<PluginIpcStats>,
}

impl ActiveClientGuard {
    fn new(stats: Arc<PluginIpcStats>) -> Self {
        stats.active_clients.fetch_add(1, Ordering::Relaxed);
        Self { stats }
    }
}

impl Drop for ActiveClientGuard {
    fn drop(&mut self) {
        self.stats.active_clients.fetch_sub(1, Ordering::Relaxed);
    }
}

#[cfg(any(target_os = "linux", target_os = "android"))]
fn peer_euid(stream: &tokio::net::UnixStream) -> Result<u32> {
    let fd = stream.as_raw_fd();
    let mut cred = libc::ucred {
        pid: 0,
        uid: 0,
        gid: 0,
    };
    let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;

    let rc = unsafe {
        libc::getsockopt(
            fd,
            libc::SOL_SOCKET,
            libc::SO_PEERCRED,
            (&mut cred as *mut libc::ucred).cast(),
            &mut len,
        )
    };

    if rc != 0 {
        return Err(FalcornError::runtime(format!(
            "Failed to query SO_PEERCRED: {}",
            std::io::Error::last_os_error()
        )));
    }
    Ok(cred.uid as u32)
}

#[cfg(any(
    target_os = "macos",
    target_os = "ios",
    target_os = "freebsd",
    target_os = "openbsd",
    target_os = "netbsd",
    target_os = "dragonfly"
))]
fn peer_euid(stream: &tokio::net::UnixStream) -> Result<u32> {
    let fd = stream.as_raw_fd();
    let mut euid: libc::uid_t = 0;
    let mut egid: libc::gid_t = 0;

    let rc = unsafe { libc::getpeereid(fd, &mut euid, &mut egid) };
    if rc != 0 {
        return Err(FalcornError::runtime(format!(
            "Failed to query getpeereid: {}",
            std::io::Error::last_os_error()
        )));
    }

    Ok(euid as u32)
}

#[cfg(not(any(
    target_os = "linux",
    target_os = "android",
    target_os = "macos",
    target_os = "ios",
    target_os = "freebsd",
    target_os = "openbsd",
    target_os = "netbsd",
    target_os = "dragonfly"
)))]
fn peer_euid(_stream: &tokio::net::UnixStream) -> Result<u32> {
    Err(FalcornError::runtime(
        "Peer credential lookup is not supported on this OS",
    ))
}

async fn open_writer(path: Option<&str>) -> Option<BufWriter<tokio::fs::File>> {
    let path = match path {
        Some(p) => p,
        None => return None,
    };

    let file = OpenOptions::new()
        .create(true)
        .append(true)
        .open(path)
        .await
        .ok()?;
    Some(BufWriter::new(file))
}

async fn flush_buf(writer: &mut Option<BufWriter<tokio::fs::File>>, buf: &mut String) {
    if buf.is_empty() {
        return;
    }
    if let Some(w) = writer.as_mut() {
        let _ = w.write_all(buf.as_bytes()).await;
        let _ = w.flush().await;
    }
    buf.clear();
}

fn format_ts(ts_millis: u64) -> String {
    let dt = chrono::DateTime::from_timestamp_millis(ts_millis as i64)
        .unwrap_or_else(|| Local::now().into());
    dt.format("%Y-%m-%d %H:%M:%S%.3f").to_string()
}

fn format_access_log(
    fmt: &str,
    remote: &str,
    method: &str,
    uri: &str,
    version: &str,
    status: u16,
    size: usize,
    ts: &str,
) -> String {
    let request_line = format!("{} {} {}", method, uri, version);

    fmt.replace("%(h)s", remote)
        .replace("%(l)s", "-")
        .replace("%(u)s", "-")
        .replace("%(t)s", ts)
        .replace("%(r)s", &request_line)
        .replace("%(s)s", &status.to_string())
        .replace("%(b)s", &size.to_string())
}

fn format_metrics_line(snapshot: &crate::logging::metrics::MetricsSnapshot) -> String {
    format!(
        "METRICS total_requests={} rps={:.2} success_rate={:.4} failure_rate={:.4} avg_us={} min_us={} max_us={} status_2xx={} status_3xx={} status_4xx={} status_5xx={}",
        snapshot.total_requests,
        snapshot.requests_per_second(),
        snapshot.success_rate(),
        snapshot.failure_rate(),
        snapshot.avg_response_time.as_micros(),
        snapshot.min_response_time.as_micros(),
        snapshot.max_response_time.as_micros(),
        snapshot.status_2xx,
        snapshot.status_3xx,
        snapshot.status_4xx,
        snapshot.status_5xx
    )
}

struct CpuTracker {
    prev_total_jiffies: Option<u64>,
    prev_proc_jiffies: HashMap<i32, u64>,
}

impl CpuTracker {
    fn new() -> Self {
        Self {
            prev_total_jiffies: None,
            prev_proc_jiffies: HashMap::new(),
        }
    }

    fn read_total_jiffies(&self) -> Option<u64> {
        read_total_jiffies()
    }

    fn read_pid_cpu_percent(&mut self, pid: i32, total_jiffies: Option<u64>) -> Option<f64> {
        let total_jiffies = total_jiffies?;
        let proc_jiffies = read_pid_jiffies(pid)?;
        let prev_total = self.prev_total_jiffies?;
        let prev_proc = self.prev_proc_jiffies.insert(pid, proc_jiffies)?;

        let delta_total = total_jiffies.saturating_sub(prev_total);
        let delta_proc = proc_jiffies.saturating_sub(prev_proc);

        if delta_total == 0 {
            return None;
        }
        let cpu_count = num_cpus::get().max(1) as f64;
        Some((delta_proc as f64 / delta_total as f64) * 100.0 * cpu_count)
    }

    fn commit(&mut self, total_jiffies: Option<u64>) {
        if let Some(total) = total_jiffies {
            self.prev_total_jiffies = Some(total);
        }
    }
}

#[cfg(target_os = "linux")]
fn read_total_jiffies() -> Option<u64> {
    let stat = std::fs::read_to_string("/proc/stat").ok()?;
    let mut lines = stat.lines();
    let cpu_line = lines.next()?;
    if !cpu_line.starts_with("cpu ") {
        return None;
    }
    let total: u64 = cpu_line
        .split_whitespace()
        .skip(1)
        .filter_map(|v| v.parse::<u64>().ok())
        .sum();
    Some(total)
}

#[cfg(not(target_os = "linux"))]
fn read_total_jiffies() -> Option<u64> {
    None
}

#[cfg(target_os = "linux")]
fn read_pid_jiffies(pid: i32) -> Option<u64> {
    let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
    let end = stat.rfind(')')?;
    let after = &stat[end + 1..];
    let mut parts = after.split_whitespace();
    // Fields: state(0), ppid(1), ..., utime(11), stime(12)
    for _ in 0..11 {
        parts.next()?;
    }
    let utime: u64 = parts.next()?.parse().ok()?;
    let stime: u64 = parts.next()?.parse().ok()?;
    Some(utime.saturating_add(stime))
}

#[cfg(not(target_os = "linux"))]
fn read_pid_jiffies(_pid: i32) -> Option<u64> {
    None
}

#[cfg(target_os = "linux")]
fn read_pid_mem_bytes(pid: i32) -> Option<u64> {
    let statm = std::fs::read_to_string(format!("/proc/{}/statm", pid)).ok()?;
    let mut parts = statm.split_whitespace();
    parts.next()?; // total pages
    let rss_pages: u64 = parts.next()?.parse().ok()?;
    let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
    if page_size <= 0 {
        return None;
    }
    Some(rss_pages.saturating_mul(page_size as u64))
}

#[cfg(not(target_os = "linux"))]
fn read_pid_mem_bytes(_pid: i32) -> Option<u64> {
    None
}

// TESTS
#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::LogLevel;
    use crate::logging::ipc::{PLUGIN_FILTER_ACCESS_LOG, PLUGIN_FILTER_METRICS};
    use tokio::net::UnixStream;

    fn test_socket_path(suffix: &str) -> String {
        let base = std::env::var("CARGO_TARGET_TMPDIR").unwrap_or_else(|_| "target".to_string());
        let dir = std::path::Path::new(&base).join("test-sockets");
        let _ = std::fs::create_dir_all(&dir);
        dir.join(format!(
            "falcorn-plugin-test-{}-{}-{}.sock",
            std::process::id(),
            now_millis(),
            suffix
        ))
        .to_string_lossy()
        .to_string()
    }

    async fn connect_and_subscribe(
        path: &str,
        token: Option<&str>,
        filter_mask: u8,
    ) -> (UnixStream, PluginSubscribeAck) {
        let mut stream = timeout(Duration::from_secs(2), UnixStream::connect(path))
            .await
            .expect("connect timeout")
            .expect("connect unix socket");

        let subscribe = PluginSubscribeRequest {
            filter_mask,
            auth_token: token.map(|s| s.to_string()),
            client_name: Some("test-client".to_string()),
        };
        write_typed_frame(&mut stream, PluginFrameType::Subscribe, &subscribe)
            .await
            .expect("send subscribe");

        let (ft, payload) = timeout(Duration::from_secs(2), read_frame(&mut stream))
            .await
            .expect("read ack timeout")
            .expect("read ack frame");
        assert_eq!(ft, PluginFrameType::Ack);
        let ack: PluginSubscribeAck = bincode::deserialize(&payload).expect("decode ack");

        (stream, ack)
    }

    async fn setup_or_skip(path: &str, token: Option<String>) -> Option<PluginBus> {
        let settings = PluginBackpressureSettings {
            drop_policy: PluginDropPolicy::DropOldest,
            max_lagged_events: 2048,
            write_timeout: Duration::from_millis(500),
            bus_capacity: 1024,
            max_clients: 64,
            subscribe_timeout: Duration::from_secs(5),
            auth_fail_window: Duration::from_secs(60),
            auth_fail_max_attempts: 10,
            acl_mode: PluginAclMode::Off,
            socket_mode: 0o660,
        };
        match setup_plugin_ipc(path, token, settings).await {
            Ok(bus) => Some(bus),
            Err(e) => {
                let msg = e.to_string().to_lowercase();
                if msg.contains("operation not permitted") || msg.contains("permission denied") {
                    eprintln!(
                        "skipping unix-socket integration test in restricted sandbox: {}",
                        e
                    );
                    None
                } else {
                    panic!("setup plugin ipc failed: {}", e);
                }
            }
        }
    }

    async fn read_event_with_ping_handling(
        stream: &mut UnixStream,
        timeout_secs: u64,
    ) -> PluginEvent {
        loop {
            let (ft, payload) = timeout(Duration::from_secs(timeout_secs), read_frame(stream))
                .await
                .expect("read frame timeout")
                .expect("read frame");
            match ft {
                PluginFrameType::Event => {
                    return bincode::deserialize::<PluginEvent>(&payload).expect("decode event");
                }
                PluginFrameType::Ping => {
                    let ping =
                        bincode::deserialize::<PluginPingFrame>(&payload).expect("decode ping");
                    let pong = PluginPongFrame {
                        ts_millis: ping.ts_millis,
                    };
                    write_typed_frame(stream, PluginFrameType::Pong, &pong)
                        .await
                        .expect("send pong");
                }
                other => panic!("unexpected frame while waiting event: {:?}", other),
            }
        }
    }

    #[tokio::test]
    async fn test_plugin_ipc_auth_success_and_event_delivery() {
        let path = test_socket_path("auth-success");
        let Some(bus) = setup_or_skip(&path, Some("secret-token".to_string())).await else {
            return;
        };

        let (mut stream, ack) =
            connect_and_subscribe(&path, Some("secret-token"), PLUGIN_FILTER_ACCESS_LOG).await;
        assert_eq!(ack.protocol_version, PLUGIN_PROTOCOL_VERSION);
        assert_eq!(ack.accepted_filter_mask, PLUGIN_FILTER_ACCESS_LOG);

        let _ = bus.sender.send(PluginEvent::AccessLog {
            ts_millis: now_millis(),
            remote: "127.0.0.1".to_string(),
            method: "GET".to_string(),
            uri: "/ok".to_string(),
            version: "HTTP/1.1".to_string(),
            status: 200,
            size: 12,
            duration_micros: 300,
        });

        let evt = read_event_with_ping_handling(&mut stream, 2).await;
        match evt {
            PluginEvent::AccessLog { uri, status, .. } => {
                assert_eq!(uri, "/ok");
                assert_eq!(status, 200);
            }
            _ => panic!("expected access event"),
        }

        let _ = std::fs::remove_file(&path);
    }

    #[tokio::test]
    async fn test_plugin_ipc_auth_failure() {
        let path = test_socket_path("auth-fail");
        let Some(_bus) = setup_or_skip(&path, Some("expected-token".to_string())).await else {
            return;
        };

        let mut stream = timeout(Duration::from_secs(2), UnixStream::connect(&path))
            .await
            .expect("connect timeout")
            .expect("connect unix socket");

        let subscribe = PluginSubscribeRequest {
            filter_mask: PLUGIN_FILTER_ACCESS_LOG,
            auth_token: Some("wrong-token".to_string()),
            client_name: Some("test-client".to_string()),
        };
        write_typed_frame(&mut stream, PluginFrameType::Subscribe, &subscribe)
            .await
            .expect("send subscribe");

        let (ft, payload) = timeout(Duration::from_secs(2), read_frame(&mut stream))
            .await
            .expect("read error timeout")
            .expect("read error frame");
        assert_eq!(ft, PluginFrameType::Error);
        let err: PluginErrorFrame = bincode::deserialize(&payload).expect("decode error");
        assert_eq!(err.code, "authentication_failed");

        let _ = std::fs::remove_file(&path);
    }

    #[tokio::test]
    async fn test_plugin_ipc_filter_and_ping_pong() {
        let path = test_socket_path("filter-ping");
        let Some(bus) = setup_or_skip(&path, None).await else {
            return;
        };

        let (mut stream, ack) = connect_and_subscribe(&path, None, PLUGIN_FILTER_METRICS).await;
        assert_eq!(ack.accepted_filter_mask, PLUGIN_FILTER_METRICS);

        // LogMessage should be filtered out
        let _ = bus.sender.send(PluginEvent::LogMessage {
            ts_millis: now_millis(),
            level: LogLevel::Info,
            pid: 123,
            worker_id: Some(1),
            message: "ignored".to_string(),
        });
        // Metrics should pass
        let _ = bus.sender.send(PluginEvent::Metrics(PluginMetricsEvent {
            uptime_secs: 120,
            total_requests: 10,
            successful_requests: 9,
            failed_requests: 1,
            requests_per_second: 100.0,
            success_rate: 0.9,
            failure_rate: 0.1,
            avg_response_time_micros: 250,
            min_response_time_micros: 100,
            max_response_time_micros: 800,
            p50_response_time_micros: None,
            p95_response_time_micros: None,
            p99_response_time_micros: None,
            p999_response_time_micros: None,
            active_connections: 1,
            total_connections: 10,
            worker_restarts: 0,
            status_2xx: 9,
            status_3xx: 0,
            status_4xx: 1,
            status_5xx: 0,
            backlog: None,
            cpu_percent: None,
            mem_bytes: None,
            error_rate: Some(0.1),
            error_4xx: Some(1),
            error_5xx: Some(0),
            top_errors: Some(vec![falcorn_proto::logging::ErrorStat {
                code: "4xx".to_string(),
                count: 1,
            }]),
            workers: None,
        }));

        let evt = read_event_with_ping_handling(&mut stream, 2).await;
        match evt {
            PluginEvent::Metrics(m) => {
                assert_eq!(m.total_requests, 10);
                assert_eq!(m.failed_requests, 1);
            }
            _ => panic!("expected metrics event"),
        }

        // Explicit ping from client should produce pong from server.
        let ping = PluginPingFrame {
            ts_millis: now_millis(),
        };
        write_typed_frame(&mut stream, PluginFrameType::Ping, &ping)
            .await
            .expect("send ping");
        let (ft, payload) = timeout(Duration::from_secs(2), read_frame(&mut stream))
            .await
            .expect("pong timeout")
            .expect("read pong");
        assert_eq!(ft, PluginFrameType::Pong);
        let pong: PluginPongFrame = bincode::deserialize(&payload).expect("decode pong");
        assert_eq!(pong.ts_millis, ping.ts_millis);

        let _ = std::fs::remove_file(&path);
    }
}
