//! Asynchronous worker implementation for non-blocking I/O operations

use crate::config::ServerConfig;
use crate::errors::{FalcornError, Result};
use crate::http::{HttpEngine, HttpRequest};
use crate::logging::ipc;
use crate::logging::{Logger, start_logger_client};
use crate::python::app::ApplicationInstance;
use crate::runtime::{BufferPool, effective_max_requests};
use crate::server::request_handler::RequestHandler;

use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::Duration;
use tokio::net::TcpListener;
use tokio::signal;
use tokio::sync::Semaphore;
use tokio::time::timeout;

/// Asynchronous worker (processes requests concurrently)
pub struct AsyncWorker {
    id: u32,
    config: Arc<ServerConfig>,
    logger: Arc<Logger>,
    app: Arc<ApplicationInstance>,
    handler: RequestHandler,
    shutdown: Arc<AtomicBool>,
    request_count: Arc<AtomicUsize>,
    conn_limit: Arc<Semaphore>,
    pool: BufferPool,
    max_requests_limit: Option<usize>,
    // buffers: WorkerBuffers
}

impl AsyncWorker {
    /// Create a new asynchronous worker
    pub fn new(id: u32, config: Arc<ServerConfig>, logger: Arc<Logger>) -> Result<Self> {
        logger.info(|| {
            format!(
                "[Worker {}] Initializing process worker (PID: {})",
                id,
                std::process::id()
            )
        });

        // Load python wsgi app
        // Each worker has a dedicated python instance so a proper GIL
        let pythonpath: Vec<std::path::PathBuf> = config
            .pythonpath
            .iter()
            .map(|p| std::path::PathBuf::from(p))
            .collect();

        let app = ApplicationInstance::load(
            &config.app_module,
            &config.app_callable,
            &pythonpath,
            &config.protocol,
            &config.asgi_event_loop,
            config.asgi_event_loop_fallback,
            config.asgi_submit_timeout_ms,
            config.asgi_max_inflight,
            config.asgi_send_pool_size,
            &config.asgi_scheduler,
        )?;

        logger.info(|| {
            format!(
                "[Worker {}] Loaded Python application: {}:{}",
                id, config.app_module, config.app_callable
            )
        });

        let handler = RequestHandler::new(app.clone(), config.protocol.clone(), config.strict_http);
        let max_conn = config.max_connections;

        let pool = BufferPool::new(config.buffer_size, 1_048_576);
        let max_requests_limit = effective_max_requests(&config, id);

        Ok(Self {
            id,
            config,
            logger,
            app,
            handler,
            shutdown: Arc::new(AtomicBool::new(false)),
            request_count: Arc::new(AtomicUsize::new(0)),
            conn_limit: Arc::new(Semaphore::new(max_conn)),
            pool,
            max_requests_limit,
            // buffers: WorkerBuffers::new(bs)
        })
    }

    /// Handle a single connection asynchronously
    /// (Main entry point)
    pub async fn run(&self) -> Result<()> {
        self.logger.info(|| {
            format!(
                "[Worker {}] Starting on {} (PID: {}, max_requests={})",
                self.id,
                self.config.bind_addr(),
                std::process::id(),
                self.max_requests_limit
                    .map(|v| v.to_string())
                    .unwrap_or_else(|| "none".to_string())
            )
        });

        // Create listener with SO_REUSEPORT
        let listener = self.create_listener().await?;

        self.logger.info(|| {
            format!(
                "[Worker {}] Successfully bound to {} with SO_REUSEPORT",
                self.id,
                self.config.bind_addr()
            )
        });

        self.start_metrics_reporter();

        // Setup signals
        let shutdown_signal = self.setup_signal_handlers();

        // Run Accept Loop
        tokio::select! {
            result = self.accept_loop(listener) => {
                if let Err(e) = result {
                    self.logger.error(|| format!(
                        "[Worker {}] Accept loop error: {}",
                        self.id, e
                    ));
                    return Err(e);
                }
            }
            _ = shutdown_signal => {
                self.logger.info(|| format!(
                    "[Worker {}] Received shutdown signal",
                    self.id
                ));
            }
        }

        self.logger.info(|| {
            format!(
                "[Worker {}] Stopped (PID: {}, {} requests processed)",
                self.id,
                std::process::id(),
                self.request_count.load(Ordering::Relaxed)
            )
        });

        Ok(())
    }

    fn start_metrics_reporter(&self) {
        if !self.config.metrics_workers_enabled {
            return;
        }
        let logger = self.logger.clone();
        let request_count = Arc::clone(&self.request_count);
        let shutdown = Arc::clone(&self.shutdown);
        let conn_limit = Arc::clone(&self.conn_limit);
        let max_conn = self.config.max_connections as usize;
        std::thread::spawn(move || {
            let interval = Duration::from_secs(1);
            while !shutdown.load(Ordering::Relaxed) {
                let available = conn_limit.available_permits();
                let active = max_conn.saturating_sub(available);
                let total_requests = request_count.load(Ordering::Relaxed) as u64;
                logger.worker_stats(active, total_requests);
                std::thread::sleep(interval);
            }
        });
    }

    /// Create TCP listener with SO_REUSEPORT
    async fn create_listener(&self) -> Result<TcpListener> {
        use std::os::unix::io::{FromRawFd, OwnedFd};

        let fd_str = std::env::var("FALCORN_LISTEN_FD")
            .map_err(|_| FalcornError::runtime("FALCORN_LISTEN_FD not set"))?;

        let fd: i32 = fd_str
            .parse()
            .map_err(|_| FalcornError::runtime("Invalid FALCORN_LISTEN_FD"))?;

        // IMPORTANT:
        // The listen FD is inherited from the master and may be shared across workers.
        // We must `dup()` it so that each worker owns its own FD handle; otherwise dropping
        // the listener in one worker can close the shared FD and cause immediate EOFs.
        let dup_fd = unsafe { libc::dup(fd) };
        if dup_fd < 0 {
            return Err(FalcornError::runtime("Failed to dup FALCORN_LISTEN_FD"));
        }

        let owned = unsafe { OwnedFd::from_raw_fd(dup_fd) };

        let std_listener = std::net::TcpListener::from(owned);

        std_listener.set_nonblocking(true).map_err(|e| {
            FalcornError::runtime(format!("Failed to set non-blocking mode: {}", e))
        })?;

        self.logger.debug(|| {
            format!(
                "[Worker {}] Listening on {} (inherited fd={}, dup fd={})",
                self.id,
                self.config.bind_addr(),
                fd,
                dup_fd
            )
        });

        // Convert into Tokio TcpListener
        let listener = TcpListener::from_std(std_listener).map_err(|e| {
            FalcornError::runtime(format!("Failed to create Tokio listener: {}", e))
        })?;

        Ok(listener)
    }

    /// Setup Signals
    fn setup_signal_handlers(&self) -> impl std::future::Future<Output = ()> {
        let shutdown = Arc::clone(&self.shutdown);
        let worker_id = self.id;
        let logger = Arc::clone(&self.logger);

        async move {
            let mut sigterm = match signal::unix::signal(signal::unix::SignalKind::terminate()) {
                Ok(s) => s,
                Err(e) => {
                    logger.error(|| {
                        format!(
                            "[Worker {}] Failed to setup SIGTERM handler: {}",
                            worker_id, e
                        )
                    });
                    shutdown.store(true, Ordering::Relaxed);
                    return;
                }
            };
            let mut sigint = match signal::unix::signal(signal::unix::SignalKind::interrupt()) {
                Ok(s) => s,
                Err(e) => {
                    logger.error(|| {
                        format!(
                            "[Worker {}] Failed to setup SIGINT handler: {}",
                            worker_id, e
                        )
                    });
                    shutdown.store(true, Ordering::Relaxed);
                    return;
                }
            };
            let mut sigquit = match signal::unix::signal(signal::unix::SignalKind::quit()) {
                Ok(s) => s,
                Err(e) => {
                    logger.error(|| {
                        format!(
                            "[Worker {}] Failed to setup SIGQUIT handler: {}",
                            worker_id, e
                        )
                    });
                    shutdown.store(true, Ordering::Relaxed);
                    return;
                }
            };

            tokio::select! {
                _ = sigterm.recv() => {
                    logger.info(|| format!("[Worker {}] Received SIGTERM", worker_id));
                }
                _ = sigint.recv() => {
                    logger.info(|| format!("[Worker {}] Received SIGINT", worker_id));
                }
                _ = sigquit.recv() => {
                    logger.info(|| format!("[Worker {}] Received SIGQUIT", worker_id));
                }
            }

            shutdown.store(true, Ordering::Relaxed);
        }
    }

    /// Connetion accept loop
    async fn accept_loop(&self, listener: TcpListener) -> Result<()> {
        self.logger.info(|| {
            format!(
                "[Worker {}] Entering accept loop, ready to receive connections",
                self.id
            )
        });

        let mut consecutive_errors = 0;
        const MAX_CONSECUTIVE_ERRORS: u32 = 10;
        // Reuse a single engine instance across all connection tasks.
        let engine = Arc::new(HttpEngine::with_pool(self.pool.clone()));

        loop {
            // Check shutdown
            if self.shutdown.load(Ordering::Relaxed) {
                break;
            }

            // Accept with timeout
            match timeout(Duration::from_millis(100), listener.accept()).await {
                Ok(Ok((stream, addr))) => {
                    consecutive_errors = 0;

                    self.logger.debug(|| {
                        format!(
                            "[Worker {}] Accepted connection from {} (total: {})",
                            self.id,
                            addr,
                            self.request_count.load(Ordering::Relaxed) + 1
                        )
                    });

                    let _ = stream.set_nodelay(true);

                    let permit = match self.conn_limit.clone().try_acquire_owned() {
                        Ok(p) => p,
                        Err(_) => {
                            self.logger.debug(|| {
                                format!(
                                    "[Worker {}] Connection refused (max_connections reached)",
                                    self.id
                                )
                            });
                            continue;
                        }
                    };

                    // Clone needed ressources forr the task
                    let handler = self.handler.clone();
                    let logger = (*self.logger).clone();
                    let request_count = Arc::clone(&self.request_count);
                    let config = Arc::clone(&self.config);
                    let engine = Arc::clone(&engine);

                    // Spawn a task to handle the connection
                    tokio::spawn(async move {
                        let _permit = permit;

                        if let Err(e) = engine
                            .handle_connection_async(
                                stream,
                                addr,
                                handler,
                                logger.clone(),
                                config,
                                Some(request_count),
                            )
                            .await
                        {
                            if e.is_recoverable() {
                                logger.warn(|| {
                                    format!("[Worker] Recoverable connection error: {}", e)
                                });
                            } else {
                                logger.error(|| format!("[Worker] Connection error: {}", e));
                            }
                        }
                    });

                    // Check for max_requests
                    if let Some(max) = self.max_requests_limit {
                        let count = self.request_count.load(Ordering::Relaxed);
                        if count >= max {
                            self.logger.info(|| {
                                format!(
                                    "[Worker {}] Max requests ({}) reached, shutting down",
                                    self.id, max
                                )
                            });
                            break;
                        }
                    }
                }
                Ok(Err(e)) => {
                    consecutive_errors += 1;

                    if !self.shutdown.load(Ordering::Relaxed) {
                        self.logger.error(|| {
                            format!(
                                "[Worker {}] Accept error (#{}/{}): {}",
                                self.id, consecutive_errors, MAX_CONSECUTIVE_ERRORS, e
                            )
                        });

                        if consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
                            return Err(FalcornError::runtime(format!(
                                "Too many consecutive accept errors: {}",
                                e
                            )));
                        }

                        // Backoff
                        tokio::time::sleep(Duration::from_millis(100 * consecutive_errors as u64))
                            .await;
                    }
                }
                Err(_) => {
                    // Timeout, continue
                    continue;
                }
            }
        }

        self.logger
            .info(|| format!("[Worker {}] Exiting accept loop", self.id));

        Ok(())
    }

    // Log access (legacy path)
    fn log_access(logger: &Logger, _worker_id: u32, request: &HttpRequest, status: u16) {
        let record = crate::logging::ipc::AccessRecord {
            remote: request.peer_addr.ip().to_string(),
            method: request.method.as_str().to_string(),
            uri: request.uri(),
            version: request.version_str().to_string(),
            status,
            size: 0,
            duration_micros: 0,
        };
        logger.access_record(record);
    }
}

/// Async mode entry point
pub async fn run_async_worker_process(worker_id: u32, config: ServerConfig) -> Result<()> {
    // Create a logger for the worker
    let socket = ipc::socket_path();
    let tx = start_logger_client(&socket);
    let logger = Arc::new(
        Logger::new(config.log_level_enum(), tx, config.log_stdout)
            .with_access_enabled(config.access_events_enabled())
            .with_worker_id(worker_id),
    );

    logger.info(|| {
        format!(
            "[Worker {}] Process started (PID: {})",
            worker_id,
            std::process::id()
        )
    });

    // Create and run async worker
    let worker = AsyncWorker::new(worker_id, Arc::new(config), logger.clone())?;

    let result = worker.run().await;

    match &result {
        Ok(_) => logger.info(|| {
            format!(
                "[Worker {}] Process exiting normally (PID: {})",
                worker_id,
                std::process::id()
            )
        }),
        Err(e) => logger.error(|| {
            format!(
                "[Worker {}] Process exiting with error (PID: {}): {}",
                worker_id,
                std::process::id(),
                e
            )
        }),
    }

    result
}

/// Helper to run Tokio runtime
pub fn run_async_worker_with_runtime(worker_id: u32, config: ServerConfig) -> Result<()> {
    // Create Tokio multi-thread runtime
    let runtime = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(config.async_worker_threads)
        .max_blocking_threads(config.async_max_blocking_threads)
        .thread_name(format!("async-worker-{}", worker_id))
        .enable_all()
        .build()
        .map_err(|e| FalcornError::runtime(format!("Failed to create Tokio runtime: {}", e)))?;

    // run the worker in the runtime
    runtime.block_on(run_async_worker_process(worker_id, config))
}
