use std::thread::available_parallelism;

use tokio::{
    net::TcpListener,
    select,
    signal::unix::{signal, SignalKind},
};

use crate::{
    handlers::handler::handle_connection,
    handling::routes::{Route, Uri},
};

#[derive(Clone)]
pub struct MountPoint<'a> {
    pub mountpoint: Uri<'a>,
    pub routes: Vec<Route<'a>>,
}

pub struct Config {
    mountpoints: Option<Vec<MountPoint<'static>>>,
    address: TcpListener,
}

impl<'a> Config {
    fn check_mountpoint_taken(&self, to_insert: Uri) -> bool {
        if let Some(to_check) = &self.mountpoints {
            for i in to_check.iter() {
                if i.mountpoint == to_insert {
                    return true; // Found a duplicate &str
                }
            }
        };
        false
    }
    pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Self {
        if self.check_mountpoint_taken(mountpoint) {
            eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{mountpoint}` already taken.\x1b[0m");
            return self;
        }
        routes.sort_by(|a, b| a.rank.cmp(&b.rank));
        let mut mount_message = format!("  >> \x1b[35m{}\x1b[0m\n", mountpoint);
        for (index, route) in routes.iter().enumerate() {
            let indent_sign = match index {
                i if i == routes.len() - 1 => "└─",
                _ => "├─",
            };

            mount_message += &format!(
            "     \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m \x1b[34;4m{}\x1b[24m{}\x1b[0m\n",
                route.name.unwrap_or(""),
                route.method,
                mountpoint,
                route.uri
            )
        }

        println!("{mount_message}");
        let mut temp_mountpoints = None;
        std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints);

        if let Some(mut mountpoints) = temp_mountpoints {
            mountpoints.push(MountPoint { mountpoint, routes });
            self.mountpoints = Some(mountpoints);
        } else {
            self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]);
        }
        self
    }
    /// # Launches/Starts the webserver
    /// Launches a Webserver Configuration
    ///
    /// Is Async
    ///
    /// Is Blocking -> Can be interrupted with ^C
    ///
    /// # Panics
    /// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there
    /// is no interrupt signal
    pub async fn launch(self) {
        println!(
            "Server launched from http://{}",
            self.address.local_addr().unwrap()
        );
        let mut sigint = signal(SignalKind::interrupt()).unwrap();
        loop {
            select! {
                _ = sigint.recv() => {
                    println!("Shutting down...");
                    break;
                }
                Ok((socket, _)) = self.address.accept() => {
                    let mountpoints = self.mountpoints.clone().unwrap();
                    tokio::spawn(async move { handle_connection(socket, mountpoints).await; });
                }
            }
        }
    }
}
/// # Creates a Webserver Config which can be launched with the launch function
/// Takes the IP and Port as an argument
///
/// Prints out the configuration test
///
/// Is async
///
/// # Example
/// ```
/// async fn example() {
///     let _ = http::build("127.0.0.1:8000");
/// }
/// ```
/// # Panics
/// Panics if the IP is not bindable, or other forms of system errors or it's not a valid
/// IP-Address
pub async fn build(ip: &str) -> Config {
    let listener = if let Ok(listener) = TcpListener::bind(ip).await {
        listener
    } else {
        panic!("\x1b[31mCould't bind Listener to address\x1b[0m");
    };
    let ip = ip.splitn(2, ':').collect::<Vec<&str>>();
    if ip.len() != 2 {
        panic!("Invalid IP Address");
    }
    let port = ip[1];
    let ip = ip[0];
    let workers = available_parallelism().unwrap().get();
    println!(
        "\x1b[34m⚙ Configuration\x1b[0m
  >> \x1b[34mIp\x1b[0m: {ip}
  >> \x1b[34mPort\x1b[0m: {port}
  >> \x1b[34mWorkers\x1b[0m: {workers}
\x1b[35m🛪 Mountpoints\x1b[0m"
    );
    Config {
        mountpoints: None,
        address: listener,
    }
}