// src/server.rs
use crate::error::ChopinError;
use crate::router::Router;
use crate::syscalls::{self};
use crate::worker::Worker;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;

pub struct Chopin {
    router: Router,
}

impl Default for Chopin {
    fn default() -> Self {
        Self::new()
    }
}

impl Chopin {
    pub fn new() -> Self {
        Self {
            router: Router::new(),
        }
    }

    pub fn mount_all_routes(mut self) -> Self {
        for route in inventory::iter::<crate::router::RouteDef> {
            self.router.add(route.method, route.path, route.handler);
        }
        self.router.finalize();
        self
    }

    pub fn serve(self, host_port: &str) -> crate::error::ChopinResult<()> {
        let server = Server::bind(host_port);
        server.serve(self.router)
    }
}

pub struct Server {
    host_port: String,
    workers: usize,
}

impl Server {
    pub fn bind(host_port: &str) -> Self {
        Self {
            host_port: host_port.to_string(),
            workers: num_cpus::get(),
        }
    }

    pub fn workers(mut self, workers: usize) -> Self {
        self.workers = workers;
        self
    }

    pub fn serve(self, mut router: Router) -> crate::error::ChopinResult<()> {
        // Sort children at every trie level for binary-search matching.
        router.finalize();

        let core_ids = core_affinity::get_core_ids().unwrap_or_default();
        let shutdown_flag = Arc::new(AtomicBool::new(false));

        let shutdown_signal = shutdown_flag.clone();
        ctrlc::set_handler(move || {
            shutdown_signal.store(true, Ordering::Release);
        })
        .map_err(|e| ChopinError::Other(format!("Failed to set Ctrl-C handler: {e}")))?;

        let mut worker_metrics = Vec::with_capacity(self.workers);
        for _ in 0..self.workers {
            worker_metrics.push(Arc::new(crate::metrics::WorkerMetrics::new()));
        }

        let _metrics_clones = worker_metrics.clone();
        let _shutdown_metrics = shutdown_flag.clone();

        let Parts { host, port } = parse_host_port(&self.host_port)?;

        let mut handles: Vec<thread::JoinHandle<()>> = Vec::with_capacity(self.workers);
        for (i, metrics_worker) in worker_metrics.iter().enumerate().take(self.workers) {
            let core_id = core_ids.get(i % core_ids.len()).copied();
            let router_clone = router.clone();
            let shutdown = shutdown_flag.clone();
            let metrics_worker = metrics_worker.clone();

            let host_clone = host.clone();
            let port_clone = port;

            let handle = thread::Builder::new()
                .name(format!("chopin-worker-{}", i))
                .spawn(move || {
                    if let Some(id) = core_id {
                        core_affinity::set_for_current(id);
                    }

                    // Create dedicated SO_REUSEPORT listener for this worker
                    match syscalls::create_listen_socket_reuseport(&host_clone, port_clone) {
                        Ok(listen_fd) => {
                            let mut worker =
                                Worker::new(i, router_clone, metrics_worker, listen_fd);
                            if let Err(_e) = worker.run(shutdown) {
                                // Error suppressed in production
                            }
                            unsafe {
                                libc::close(listen_fd);
                            }
                        }
                        Err(_e) => {
                            // Error suppressed in production
                        }
                    }
                })
                .map_err(ChopinError::from)?;

            handles.push(handle);
        }

        for handle in handles {
            let _ = handle.join();
        }

        Ok(())
    }
}

struct Parts {
    host: String,
    port: u16,
}

fn parse_host_port(hp: &str) -> crate::error::ChopinResult<Parts> {
    let parts: Vec<&str> = hp.split(':').collect();
    let host = parts.first().unwrap_or(&"0.0.0.0").to_string();
    let port = parts
        .get(1)
        .ok_or_else(|| crate::error::ChopinError::Other("Missing port in address".to_string()))?
        .parse::<u16>()
        .map_err(|_| crate::error::ChopinError::Other("Invalid port number".to_string()))?;

    Ok(Parts { host, port })
}
