//! ASGI execution path with dedicated Python event loop runtime per worker.

use super::callback_scheduler::CallbackScheduler;
use super::{
    AsgiEnvironBuilder, AsgiResponseAdapter, AsgiSendEventPool, create_asgi_io, monotonic_now_ns,
};
use crate::errors::{FalcornError, Result};
use crate::http::{HttpRequest, HttpResponse};
use bytes::Bytes;
use crossbeam_queue::SegQueue;
use parking_lot::Mutex;
use pyo3::exceptions::PyStopIteration;
use pyo3::prelude::*;
use std::collections::VecDeque;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, mpsc as std_mpsc};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use tokio::sync::{Semaphore, oneshot};
use tokio::time;

pub struct AsgiExecutor {
    _runtime: Arc<AsgiLoopRuntime>,
    dispatchers: Vec<Arc<AsgiDispatcher>>,
    dispatch_rr: AtomicU64,
    submit_timeout: Duration,
    inflight_limit: Arc<Semaphore>,
    send_pool: Arc<AsgiSendEventPool>,
}

struct AsgiLoopRuntime {
    loop_obj: Py<PyAny>,
    call_soon_threadsafe_fn: Py<PyAny>,
    create_task_fn: Py<PyAny>,
    copy_context_fn: Py<PyAny>,
    callback_scheduler: Option<Arc<CallbackScheduler>>,
    join_handle: Mutex<Option<JoinHandle<()>>>,
}

struct AsgiDispatchJob {
    request: HttpRequest,
    receive: super::AsgiReceive,
    send: super::AsgiSend,
}

struct AsgiDispatcher {
    inner: AsgiDispatcherInner,
    mode: AsgiSchedulerKind,
}

enum AsgiDispatcherInner {
    Threaded {
        tx: std_mpsc::SyncSender<AsgiDispatchJob>,
        join_handle: Mutex<Option<JoinHandle<()>>>,
    },
    Direct {
        runtime: Arc<AsgiLoopRuntime>,
        queue: Arc<SegQueue<AsgiDispatchJob>>,
        scheduled: Arc<AtomicBool>,
        drain_cb: Py<PyAny>,
    },
}

#[derive(Clone, Copy)]
enum AsgiSchedulerKind {
    Task,
    Callback,
}

impl AsgiSchedulerKind {
    fn from_str(raw: &str) -> Result<Self> {
        match raw.to_lowercase().as_str() {
            "task" => Ok(Self::Task),
            "callback" => Ok(Self::Callback),
            other => Err(FalcornError::config(format!(
                "Invalid asgi_scheduler '{}': expected 'task' or 'callback'",
                other
            ))),
        }
    }
}

#[pyclass]
struct AsgiDrainCallback {
    pending: Arc<Mutex<VecDeque<Py<PyAny>>>>,
    call_soon_threadsafe_fn: Py<PyAny>,
    create_task_fn: Py<PyAny>,
    scheduled: Arc<AtomicBool>,
    self_ref: Arc<OnceLock<Py<PyAny>>>,
    max_batch: usize,
}

#[pymethods]
impl AsgiDrainCallback {
    fn __call__(&self, py: Python<'_>) -> PyResult<()> {
        let mut batch = Vec::with_capacity(self.max_batch);
        {
            let mut pending = self.pending.lock();
            for _ in 0..self.max_batch {
                if let Some(coro) = pending.pop_front() {
                    batch.push(coro);
                } else {
                    break;
                }
            }
        }
        let create_task_fn = self.create_task_fn.bind(py);
        for coro in batch {
            create_task_fn.call1((coro,))?;
        }

        let has_more = !self.pending.lock().is_empty();
        if has_more {
            if let Some(self_obj) = self.self_ref.get() {
                let call_soon_threadsafe_fn = self.call_soon_threadsafe_fn.bind(py);
                call_soon_threadsafe_fn.call1((self_obj,))?;
                return Ok(());
            }
        }

        self.scheduled.store(false, Ordering::Release);
        if !self.pending.lock().is_empty() && !self.scheduled.swap(true, Ordering::AcqRel) {
            if let Some(self_obj) = self.self_ref.get() {
                let call_soon_threadsafe_fn = self.call_soon_threadsafe_fn.bind(py);
                call_soon_threadsafe_fn.call1((self_obj,))?;
            }
        }
        Ok(())
    }
}

#[pyclass]
struct AsgiDirectDrain {
    queue: Arc<SegQueue<AsgiDispatchJob>>,
    scheduled: Arc<AtomicBool>,
    call_soon_threadsafe_fn: Py<PyAny>,
    scheduler: Arc<CallbackScheduler>,
    copy_context_fn: Py<PyAny>,
    app: Py<PyAny>,
    self_ref: Arc<OnceLock<Py<PyAny>>>,
    max_batch: usize,
}

#[pymethods]
impl AsgiDirectDrain {
    fn __call__(&self, py: Python<'_>) -> PyResult<()> {
        let mut processed = 0usize;
        while processed < self.max_batch {
            let Some(job) = self.queue.pop() else {
                break;
            };
            processed += 1;
            let AsgiDispatchJob {
                request,
                receive,
                send,
            } = job;
            let now = monotonic_now_ns();
            send.mark_dispatch_dequeued(now);
            send.mark_dispatch_scheduled(now);

            let result = (|| -> Result<()> {
                let scope_started = monotonic_now_ns();
                let scope = AsgiEnvironBuilder::from_request(py, &request)?;
                send.mark_scope_build_ns(monotonic_now_ns().saturating_sub(scope_started));
                send.attach_scope(scope.clone_ref(py));
                let receive_py = Py::new(py, receive)?;
                let send_py = Py::new(py, send.clone())?;
                let scope_for_call = scope.clone_ref(py);
                let coro_started = monotonic_now_ns();
                let coro = self
                    .app
                    .bind(py)
                    .call1((scope_for_call, receive_py, send_py))
                    .map_err(FalcornError::from)?;
                send.mark_coro_create_ns(monotonic_now_ns().saturating_sub(coro_started));
                let ctx = self.copy_context_fn.bind(py).call0()?.unbind();
                self.scheduler.run(py, coro.unbind(), ctx);
                Ok(())
            })();
            if let Err(err) = result {
                eprintln!("[ASGI] callback drain failed: {}", err);
                send.fail_request();
            }
        }

        if self.queue.is_empty() {
            self.scheduled.store(false, Ordering::Release);
            if !self.queue.is_empty() && !self.scheduled.swap(true, Ordering::AcqRel) {
                if let Some(self_obj) = self.self_ref.get() {
                    let call_soon = self.call_soon_threadsafe_fn.bind(py);
                    let _ = call_soon.call1((self_obj,));
                }
            }
        } else {
            if let Some(self_obj) = self.self_ref.get() {
                let call_soon = self.call_soon_threadsafe_fn.bind(py);
                let _ = call_soon.call1((self_obj,));
            }
        }
        Ok(())
    }
}

struct AsgiPerfStats {
    enabled: AtomicBool,
    calls: AtomicU64,
    total_ns: AtomicU64,
    scope_ns: AtomicU64,
    coro_ns: AtomicU64,
    submit_ns: AtomicU64,
    wait_ns: AtomicU64,
    queue_delay_ns: AtomicU64,
    dispatch_queue_ns: AtomicU64,
    dispatch_setup_ns: AtomicU64,
    loop_queue_ns: AtomicU64,
    app_exec_ns: AtomicU64,
    egress_delay_ns: AtomicU64,
    adapt_ns: AtomicU64,
}

impl AsgiPerfStats {
    fn global() -> &'static Self {
        static PERF: OnceLock<AsgiPerfStats> = OnceLock::new();
        PERF.get_or_init(|| AsgiPerfStats {
            enabled: AtomicBool::new(matches!(
                std::env::var("FALCORN_ASGI_PROFILE").ok().as_deref(),
                Some("1" | "true" | "TRUE" | "yes" | "YES" | "on" | "ON")
            )),
            calls: AtomicU64::new(0),
            total_ns: AtomicU64::new(0),
            scope_ns: AtomicU64::new(0),
            coro_ns: AtomicU64::new(0),
            submit_ns: AtomicU64::new(0),
            wait_ns: AtomicU64::new(0),
            queue_delay_ns: AtomicU64::new(0),
            dispatch_queue_ns: AtomicU64::new(0),
            dispatch_setup_ns: AtomicU64::new(0),
            loop_queue_ns: AtomicU64::new(0),
            app_exec_ns: AtomicU64::new(0),
            egress_delay_ns: AtomicU64::new(0),
            adapt_ns: AtomicU64::new(0),
        })
    }

    #[inline(always)]
    fn is_enabled(&self) -> bool {
        self.enabled.load(Ordering::Relaxed)
    }

    fn record(
        &self,
        total_ns: u64,
        scope_ns: u64,
        coro_ns: u64,
        submit_ns: u64,
        wait_ns: u64,
        queue_delay_ns: u64,
        dispatch_queue_ns: u64,
        dispatch_setup_ns: u64,
        loop_queue_ns: u64,
        app_exec_ns: u64,
        egress_delay_ns: u64,
        adapt_ns: u64,
    ) {
        if !self.is_enabled() {
            return;
        }

        let calls = self.calls.fetch_add(1, Ordering::Relaxed) + 1;
        self.total_ns.fetch_add(total_ns, Ordering::Relaxed);
        self.scope_ns.fetch_add(scope_ns, Ordering::Relaxed);
        self.coro_ns.fetch_add(coro_ns, Ordering::Relaxed);
        self.submit_ns.fetch_add(submit_ns, Ordering::Relaxed);
        self.wait_ns.fetch_add(wait_ns, Ordering::Relaxed);
        self.queue_delay_ns
            .fetch_add(queue_delay_ns, Ordering::Relaxed);
        self.dispatch_queue_ns
            .fetch_add(dispatch_queue_ns, Ordering::Relaxed);
        self.dispatch_setup_ns
            .fetch_add(dispatch_setup_ns, Ordering::Relaxed);
        self.loop_queue_ns
            .fetch_add(loop_queue_ns, Ordering::Relaxed);
        self.app_exec_ns.fetch_add(app_exec_ns, Ordering::Relaxed);
        self.egress_delay_ns
            .fetch_add(egress_delay_ns, Ordering::Relaxed);
        self.adapt_ns.fetch_add(adapt_ns, Ordering::Relaxed);

        if calls % 20_000 == 0 {
            let total = self.total_ns.load(Ordering::Relaxed);
            let scope = self.scope_ns.load(Ordering::Relaxed);
            let coro = self.coro_ns.load(Ordering::Relaxed);
            let submit = self.submit_ns.load(Ordering::Relaxed);
            let wait = self.wait_ns.load(Ordering::Relaxed);
            let queue_delay = self.queue_delay_ns.load(Ordering::Relaxed);
            let dispatch_queue = self.dispatch_queue_ns.load(Ordering::Relaxed);
            let dispatch_setup = self.dispatch_setup_ns.load(Ordering::Relaxed);
            let loop_queue = self.loop_queue_ns.load(Ordering::Relaxed);
            let app_exec = self.app_exec_ns.load(Ordering::Relaxed);
            let egress_delay = self.egress_delay_ns.load(Ordering::Relaxed);
            let adapt = self.adapt_ns.load(Ordering::Relaxed);
            let c = calls as f64;
            eprintln!(
                "[ASGI-PROFILE] calls={} avg_total_us={:.2} avg_scope_us={:.2} avg_coro_us={:.2} avg_submit_us={:.2} avg_wait_us={:.2} avg_queue_delay_us={:.2} avg_dispatch_queue_us={:.2} avg_dispatch_setup_us={:.2} avg_loop_queue_us={:.2} avg_app_exec_us={:.2} avg_egress_delay_us={:.2} avg_adapt_us={:.2}",
                calls,
                total as f64 / c / 1_000.0,
                scope as f64 / c / 1_000.0,
                coro as f64 / c / 1_000.0,
                submit as f64 / c / 1_000.0,
                wait as f64 / c / 1_000.0,
                queue_delay as f64 / c / 1_000.0,
                dispatch_queue as f64 / c / 1_000.0,
                dispatch_setup as f64 / c / 1_000.0,
                loop_queue as f64 / c / 1_000.0,
                app_exec as f64 / c / 1_000.0,
                egress_delay as f64 / c / 1_000.0,
                adapt as f64 / c / 1_000.0,
            );
        }
    }
}

impl AsgiExecutor {
    pub fn new(
        app: Py<PyAny>,
        event_loop_backend: &str,
        allow_fallback: bool,
        submit_timeout_ms: u64,
        max_inflight: usize,
        send_pool_size: usize,
        scheduler_mode: &str,
    ) -> Result<Self> {
        let scheduler_kind = AsgiSchedulerKind::from_str(scheduler_mode)?;
        let runtime = AsgiLoopRuntime::start(event_loop_backend, allow_fallback, scheduler_kind)?;
        let dispatchers_count = std::env::var("FALCORN_ASGI_DISPATCHERS")
            .ok()
            .and_then(|v| v.parse::<usize>().ok())
            .unwrap_or(1)
            .max(1);
        let mut dispatchers = Vec::with_capacity(dispatchers_count);
        for _ in 0..dispatchers_count {
            let app_ref = Python::attach(|py| app.clone_ref(py));
            dispatchers.push(AsgiDispatcher::start(
                app_ref,
                runtime.clone(),
                max_inflight,
                scheduler_kind,
            )?);
        }
        Ok(Self {
            _runtime: runtime,
            dispatchers,
            dispatch_rr: AtomicU64::new(0),
            submit_timeout: Duration::from_millis(submit_timeout_ms.max(1)),
            inflight_limit: Arc::new(Semaphore::new(max_inflight.max(1))),
            send_pool: AsgiSendEventPool::new(send_pool_size),
        })
    }

    pub async fn call(&self, request: &HttpRequest) -> Result<HttpResponse> {
        let _permit = self
            .inflight_limit
            .acquire()
            .await
            .map_err(|_| FalcornError::runtime("ASGI inflight limiter is closed"))?;

        let idx =
            (self.dispatch_rr.fetch_add(1, Ordering::Relaxed) as usize) % self.dispatchers.len();
        let dispatcher = &self.dispatchers[idx];
        call_asgi_via_runtime(
            dispatcher,
            request,
            self.submit_timeout,
            self.send_pool.clone(),
        )
        .await
    }
}

impl AsgiDispatcher {
    fn start(
        app: Py<PyAny>,
        runtime: Arc<AsgiLoopRuntime>,
        queue_capacity: usize,
        mode: AsgiSchedulerKind,
    ) -> Result<Arc<Self>> {
        if matches!(mode, AsgiSchedulerKind::Callback) {
            if runtime.callback_scheduler.is_none() {
                return Err(FalcornError::runtime(
                    "Callback scheduler is not initialized",
                ));
            }
            let queue = Arc::new(SegQueue::new());
            let scheduled = Arc::new(AtomicBool::new(false));
            let self_ref = Arc::new(OnceLock::new());
            let drain_cb = Python::attach(|py| -> Result<Py<PyAny>> {
                let cb = Py::new(
                    py,
                    AsgiDirectDrain {
                        queue: queue.clone(),
                        scheduled: scheduled.clone(),
                        call_soon_threadsafe_fn: runtime.call_soon_threadsafe_fn.clone_ref(py),
                        scheduler: runtime
                            .callback_scheduler
                            .as_ref()
                            .ok_or_else(|| {
                                FalcornError::runtime("Callback scheduler not initialized")
                            })?
                            .clone(),
                        copy_context_fn: runtime.copy_context_fn.clone_ref(py),
                        app: app.clone_ref(py),
                        self_ref: self_ref.clone(),
                        max_batch: std::env::var("FALCORN_ASGI_DRAIN_BATCH")
                            .ok()
                            .and_then(|v| v.parse::<usize>().ok())
                            .unwrap_or(128)
                            .max(1),
                    },
                )
                .map_err(FalcornError::from)?;
                let cb_any = cb.into_any();
                let _ = self_ref.set(cb_any.clone_ref(py));
                Ok(cb_any)
            })?;
            return Ok(Arc::new(Self {
                inner: AsgiDispatcherInner::Direct {
                    runtime,
                    queue,
                    scheduled,
                    drain_cb,
                },
                mode,
            }));
        }

        // Keep inflight backpressure at the executor level, but give dispatcher
        // a wider buffer to avoid producer-side stalls on bursty workloads.
        let dispatch_capacity = queue_capacity.saturating_mul(4).max(8192);
        let (tx, rx) = std_mpsc::sync_channel::<AsgiDispatchJob>(dispatch_capacity);
        let pending_coros = Arc::new(Mutex::new(VecDeque::with_capacity(dispatch_capacity)));
        let drain_scheduled = Arc::new(AtomicBool::new(false));
        let drain_self_ref = Arc::new(OnceLock::new());
        let drain_batch = std::env::var("FALCORN_ASGI_DRAIN_BATCH")
            .ok()
            .and_then(|v| v.parse::<usize>().ok())
            .unwrap_or(128)
            .max(1);
        let inline_fastpath = !matches!(
            std::env::var("FALCORN_ASGI_INLINE_FASTPATH")
                .ok()
                .as_deref(),
            Some("0" | "false" | "FALSE" | "no" | "NO" | "off" | "OFF")
        );
        let drain_callback = Python::attach(|py| -> Result<Py<PyAny>> {
            let cb = Py::new(
                py,
                AsgiDrainCallback {
                    pending: pending_coros.clone(),
                    call_soon_threadsafe_fn: runtime.call_soon_threadsafe_fn.clone_ref(py),
                    create_task_fn: runtime.create_task_fn.clone_ref(py),
                    scheduled: drain_scheduled.clone(),
                    self_ref: drain_self_ref.clone(),
                    max_batch: drain_batch,
                },
            )
            .map_err(FalcornError::from)?;
            let cb_any = cb.into_any();
            let _ = drain_self_ref.set(cb_any.clone_ref(py));
            Ok(cb_any)
        })?;
        let join_handle = thread::Builder::new()
            .name("falcorn-asgi-dispatch".to_string())
            .spawn(move || {
                while let Ok(job) = rx.recv() {
                    let AsgiDispatchJob {
                        request,
                        receive,
                        send,
                    } = job;
                    match mode {
                        AsgiSchedulerKind::Task => {
                            send.mark_dispatch_dequeued(monotonic_now_ns());
                            let send_on_fail = send.clone();
                            let result = Python::attach(|py| -> Result<()> {
                                let scope_started = monotonic_now_ns();
                                let scope = AsgiEnvironBuilder::from_request(py, &request)?;
                                send.mark_scope_build_ns(
                                    monotonic_now_ns().saturating_sub(scope_started),
                                );
                                send.attach_scope(scope.clone_ref(py));
                                let receive_py = Py::new(py, receive)?;
                                let send_py = Py::new(py, send.clone())?;
                                let scope_for_call = scope.clone_ref(py);
                                let coro_started = monotonic_now_ns();
                                let coro = app
                                    .bind(py)
                                    .call1((scope_for_call, receive_py, send_py))
                                    .map_err(FalcornError::from)?;
                                send.mark_coro_create_ns(
                                    monotonic_now_ns().saturating_sub(coro_started),
                                );

                                if inline_fastpath && Self::try_run_inline(py, &coro)? {
                                    // Completed inline: no event-loop scheduling required.
                                    return Ok(());
                                }

                                send.mark_dispatch_scheduled(monotonic_now_ns());
                                pending_coros.lock().push_back(coro.unbind());
                                if !drain_scheduled.swap(true, Ordering::AcqRel) {
                                    let call_soon_threadsafe_fn =
                                        runtime.call_soon_threadsafe_fn.bind(py);
                                    call_soon_threadsafe_fn.call1((drain_callback.bind(py),))?;
                                }
                                Ok(())
                            });
                            if let Err(err) = result {
                                eprintln!("[ASGI] dispatcher submit failed: {}", err);
                                send_on_fail.fail_request();
                            }
                        }
                        AsgiSchedulerKind::Callback => {}
                    }
                }
            })
            .map_err(|e| {
                FalcornError::runtime(format!("Failed to spawn ASGI dispatcher thread: {}", e))
            })?;

        Ok(Arc::new(Self {
            inner: AsgiDispatcherInner::Threaded {
                tx,
                join_handle: Mutex::new(Some(join_handle)),
            },
            mode,
        }))
    }

    fn submit(&self, job: AsgiDispatchJob) -> Result<()> {
        match &self.inner {
            AsgiDispatcherInner::Threaded { tx, .. } => tx
                .send(job)
                .map_err(|_| FalcornError::runtime("ASGI dispatcher queue is closed")),
            AsgiDispatcherInner::Direct {
                runtime,
                queue,
                scheduled,
                drain_cb,
                ..
            } => {
                queue.push(job);
                if !scheduled.swap(true, Ordering::AcqRel) {
                    Python::attach(|py| -> Result<()> {
                        let call_soon = runtime.call_soon_threadsafe_fn.bind(py);
                        call_soon.call1((drain_cb.clone_ref(py),))?;
                        Ok(())
                    })
                    .map_err(FalcornError::from)?;
                }
                Ok(())
            }
        }
    }

    fn try_run_inline(py: Python<'_>, coro: &Bound<'_, PyAny>) -> Result<bool> {
        match coro.call_method1("send", (py.None(),)) {
            Ok(_) => Ok(false),
            Err(err) => {
                if err.is_instance_of::<PyStopIteration>(py) {
                    Ok(true)
                } else {
                    Err(FalcornError::from(err))
                }
            }
        }
    }
}

impl Drop for AsgiDispatcher {
    fn drop(&mut self) {
        if let AsgiDispatcherInner::Threaded { tx, join_handle } = &mut self.inner {
            let replacement_tx = std_mpsc::sync_channel::<AsgiDispatchJob>(1).0;
            let _ = std::mem::replace(tx, replacement_tx);
            let mut guard = join_handle.lock();
            if let Some(handle) = guard.take() {
                let _ = handle.join();
            }
        }
    }
}

impl AsgiLoopRuntime {
    fn start(
        event_loop_backend: &str,
        allow_fallback: bool,
        scheduler_kind: AsgiSchedulerKind,
    ) -> Result<Arc<Self>> {
        let backend = event_loop_backend.to_lowercase();
        if backend != "asyncio" && backend != "uvloop" {
            return Err(FalcornError::config(format!(
                "Invalid asgi_event_loop '{}': expected 'asyncio' or 'uvloop'",
                event_loop_backend
            )));
        }

        let (init_tx, init_rx) = std_mpsc::channel::<
            std::result::Result<
                (
                    Py<PyAny>,
                    Py<PyAny>,
                    Py<PyAny>,
                    Py<PyAny>,
                    Option<CallbackScheduler>,
                ),
                String,
            >,
        >();
        let thread_backend = backend.clone();
        let join_handle = thread::Builder::new()
            .name("falcorn-asgi-loop".to_string())
            .spawn(move || {
                Python::attach(|py| {
                    let setup_result: std::result::Result<
                        (
                            Py<PyAny>,
                            Py<PyAny>,
                            Py<PyAny>,
                            Py<PyAny>,
                            Option<CallbackScheduler>,
                        ),
                        String,
                    > = (|| {
                        let asyncio = py
                            .import("asyncio")
                            .map_err(|e| format!("Failed to import asyncio: {}", e))?;

                        if thread_backend == "uvloop" {
                            match py.import("uvloop") {
                                Ok(uvloop) => {
                                    uvloop
                                        .call_method0("install")
                                        .map_err(|e| format!("Failed to install uvloop: {}", e))?;
                                }
                                Err(err) => {
                                    if !allow_fallback {
                                        return Err(format!(
                                            "Failed to import uvloop and fallback disabled: {}",
                                            err
                                        ));
                                    }
                                    eprintln!(
                                        "[ASGI] uvloop unavailable, falling back to asyncio: {}",
                                        err
                                    );
                                }
                            }
                        }

                        let loop_obj = asyncio
                            .call_method0("new_event_loop")
                            .map_err(|e| format!("Failed to create event loop: {}", e))?;
                        asyncio
                            .call_method1("set_event_loop", (loop_obj.clone(),))
                            .map_err(|e| format!("Failed to set event loop: {}", e))?;

                        // Optional perf mode: eager task factory (Python 3.12+).
                        // This can reduce scheduler queueing for short-lived ASGI coroutines.
                        let eager_enabled = !matches!(
                            std::env::var("FALCORN_ASGI_EAGER_TASKS").ok().as_deref(),
                            Some("0" | "false" | "FALSE" | "no" | "NO" | "off" | "OFF")
                        );
                        if eager_enabled
                            && let Ok(eager_factory) = asyncio.getattr("eager_task_factory")
                        {
                            let _ = loop_obj.call_method1("set_task_factory", (eager_factory,));
                        }

                        let call_soon_threadsafe_fn = loop_obj
                            .getattr("call_soon_threadsafe")
                            .map_err(|e| format!("Failed to get loop.call_soon_threadsafe: {}", e))?
                            .unbind();

                        let create_task_fn = loop_obj
                            .getattr("create_task")
                            .map_err(|e| format!("Failed to get loop.create_task: {}", e))?
                            .unbind();

                        let copy_context_fn = py
                            .import("contextvars")
                            .map_err(|e| format!("Failed to import contextvars: {}", e))?
                            .getattr("copy_context")
                            .map_err(|e| format!("Failed to get contextvars.copy_context: {}", e))?
                            .unbind();

                        let loop_unbound = loop_obj.unbind();
                        let callback_scheduler = match scheduler_kind {
                            AsgiSchedulerKind::Callback => {
                                Some(CallbackScheduler::new(py, &loop_unbound).map_err(|e| {
                                    format!("Failed to init callback scheduler: {}", e)
                                })?)
                            }
                            AsgiSchedulerKind::Task => None,
                        };

                        Ok((
                            loop_unbound,
                            call_soon_threadsafe_fn,
                            create_task_fn,
                            copy_context_fn,
                            callback_scheduler,
                        ))
                    })();

                    match setup_result {
                        Ok((
                            loop_obj,
                            call_soon_threadsafe_fn,
                            create_task_fn,
                            copy_context_fn,
                            callback_scheduler,
                        )) => {
                            let _ = init_tx.send(Ok((
                                loop_obj.clone_ref(py),
                                call_soon_threadsafe_fn.clone_ref(py),
                                create_task_fn.clone_ref(py),
                                copy_context_fn.clone_ref(py),
                                callback_scheduler,
                            )));

                            let loop_ref = loop_obj.bind(py);
                            let _ = loop_ref.call_method0("run_forever");
                            let _ = loop_ref.call_method0("close");
                        }
                        Err(e) => {
                            let _ = init_tx.send(Err(e));
                        }
                    }
                });
            })
            .map_err(|e| {
                FalcornError::runtime(format!("Failed to spawn ASGI loop thread: {}", e))
            })?;

        let (
            loop_obj,
            call_soon_threadsafe_fn,
            create_task_fn,
            copy_context_fn,
            callback_scheduler,
        ) = init_rx
            .recv_timeout(Duration::from_secs(5))
            .map_err(|_| {
                FalcornError::runtime("Timed out waiting for ASGI event loop initialization")
            })?
            .map_err(FalcornError::runtime)?;

        Ok(Arc::new(Self {
            loop_obj,
            call_soon_threadsafe_fn,
            create_task_fn,
            copy_context_fn,
            callback_scheduler: callback_scheduler.map(Arc::new),
            join_handle: Mutex::new(Some(join_handle)),
        }))
    }
}

impl Drop for AsgiLoopRuntime {
    fn drop(&mut self) {
        Python::attach(|py| {
            let loop_obj = self.loop_obj.bind(py);
            if let Ok(stop_cb) = loop_obj.getattr("stop") {
                let _ = loop_obj.call_method1("call_soon_threadsafe", (stop_cb,));
            }
        });

        let mut guard = self.join_handle.lock();
        if let Some(handle) = guard.take() {
            let _ = handle.join();
        }
    }
}

async fn call_asgi_via_runtime(
    dispatcher: &AsgiDispatcher,
    request: &HttpRequest,
    submit_timeout: Duration,
    send_pool: Arc<AsgiSendEventPool>,
) -> Result<HttpResponse> {
    let perf = AsgiPerfStats::global();
    let profiling = perf.is_enabled();
    let total_started_ns = if profiling {
        Some(monotonic_now_ns())
    } else {
        None
    };

    let body = request
        .body
        .clone()
        .unwrap_or_else(|| Arc::new(Bytes::new()));
    let (completion_tx, completion_rx) = oneshot::channel::<()>();
    let (receive, send) = create_asgi_io(body, send_pool, completion_tx, profiling);
    let submit_started_ns = if profiling {
        Some(monotonic_now_ns())
    } else {
        None
    };
    if profiling {
        send.mark_dispatch_enqueued(monotonic_now_ns());
    }
    dispatcher.submit(AsgiDispatchJob {
        request: request.clone(),
        receive,
        send: send.clone(),
    })?;
    let (scope_ns, coro_ns) = send.scope_coro_snapshot().unwrap_or((0, 0));
    let submit_finished_ns = if profiling {
        Some(monotonic_now_ns())
    } else {
        None
    };
    let submit_ns = submit_started_ns
        .zip(submit_finished_ns)
        .map(|(start, end)| end.saturating_sub(start))
        .unwrap_or(0);

    let (completion_done_ns, wait_ns) = if send.is_completed() {
        let done = if profiling {
            Some(monotonic_now_ns())
        } else {
            None
        };
        (done, 0)
    } else {
        let wait_started_ns = if profiling {
            Some(monotonic_now_ns())
        } else {
            None
        };
        match time::timeout(submit_timeout, completion_rx).await {
            Ok(Ok(())) => {}
            Ok(Err(_)) => {
                return Err(FalcornError::runtime(
                    "ASGI completion channel closed before response body finished",
                ));
            }
            Err(_) => {
                return Err(FalcornError::AsgiTimeout {
                    seconds: submit_timeout.as_secs().max(1),
                });
            }
        }
        let done = if profiling {
            Some(monotonic_now_ns())
        } else {
            None
        };
        let wait = wait_started_ns
            .zip(done)
            .map(|(start, end)| end.saturating_sub(start))
            .unwrap_or(0);
        (done, wait)
    };
    let (first_receive_ns, last_send_ns) = send.timings_snapshot().unwrap_or((0, 0));
    let (dispatch_enqueue_ns, dispatch_dequeue_ns, dispatch_schedule_ns) =
        send.dispatch_timings_snapshot().unwrap_or((0, 0, 0));
    let dispatch_queue_ns = if dispatch_enqueue_ns > 0 && dispatch_dequeue_ns > 0 {
        dispatch_dequeue_ns.saturating_sub(dispatch_enqueue_ns)
    } else {
        0
    };
    let dispatch_setup_ns = if dispatch_dequeue_ns > 0 && dispatch_schedule_ns > 0 {
        dispatch_schedule_ns.saturating_sub(dispatch_dequeue_ns)
    } else {
        0
    };
    let loop_queue_ns = if dispatch_schedule_ns > 0 && first_receive_ns > 0 {
        first_receive_ns.saturating_sub(dispatch_schedule_ns)
    } else {
        0
    };
    let queue_delay_ns = submit_finished_ns
        .map(|submitted| {
            if first_receive_ns > 0 {
                first_receive_ns.saturating_sub(submitted)
            } else {
                0
            }
        })
        .unwrap_or(0);
    let app_exec_ns = if first_receive_ns > 0 && last_send_ns > 0 {
        last_send_ns.saturating_sub(first_receive_ns)
    } else {
        0
    };
    let egress_delay_ns = completion_done_ns
        .map(|completed| {
            if last_send_ns > 0 {
                completed.saturating_sub(last_send_ns)
            } else {
                0
            }
        })
        .unwrap_or(0);

    let adapt_started_ns = if profiling {
        Some(monotonic_now_ns())
    } else {
        None
    };
    let mut response_events = send.take_events().map_err(FalcornError::from)?;
    let response = AsgiResponseAdapter::from_asgi_events(&mut response_events);
    send.recycle_events(response_events);
    let adapt_ns = adapt_started_ns
        .map(|start| monotonic_now_ns().saturating_sub(start))
        .unwrap_or(0);

    let out = response?.to_http_response();
    if let Some(started_ns) = total_started_ns {
        let total_ns = monotonic_now_ns().saturating_sub(started_ns);
        perf.record(
            total_ns,
            scope_ns,
            coro_ns,
            submit_ns,
            wait_ns,
            queue_delay_ns,
            dispatch_queue_ns,
            dispatch_setup_ns,
            loop_queue_ns,
            app_exec_ns,
            egress_delay_ns,
            adapt_ns,
        );
    }
    out
}
