use std::sync::LazyLock;

use ahash::AHashMap;
use colored::Colorize;
use parking_lot::RwLock;

use crate::error::{ZenyxError, ZenyxErrorKind};

pub static COMMAND_MANAGER: LazyLock<RwLock<CommandManager>> =
    LazyLock::new(|| RwLock::new(CommandManager::init()));

#[macro_export]
macro_rules! commands {
    [$($command:ty),*] => [
        $(
            {
                let mut manager = $crate::core::repl::handler::COMMAND_MANAGER.write();
                manager.add_command(Box::new(<$command>::default()));
            }
        )*
    ];
}

#[macro_export]
macro_rules! alias {
    ($($alias:expr => $command:expr),*) => {
        $(
            {
                let mut manager = $crate::COMMAND_MANAGER.write();
                manager.add_alias($alias, $command);
            }
        )*
    };
}

fn hamming_distance(a: &str, b: &str) -> Option<usize> {
    if a.len() != b.len() {
        return None;
    }
    Some(
        a.chars()
            .zip(b.chars())
            .filter(|(char_a, char_b)| char_a != char_b)
            .count(),
    )
}

fn edit_distance(a: &str, b: &str) -> usize {
    let m = a.len();
    let n = b.len();

    let mut dp = vec![vec![0; n + 1]; m + 1];

    for i in 0..=m {
        for j in 0..=n {
            if i == 0 {
                dp[i][j] = j;
            } else if j == 0 {
                dp[i][j] = i;
            } else if a.chars().nth(i - 1) == b.chars().nth(j - 1) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = 1 + dp[i - 1][j - 1].min(dp[i - 1][j]).min(dp[i][j - 1]);
            }
        }
    }

    dp[m][n]
}

fn check_similarity(target: &str) -> Option<String> {
    let max_hamming_distance: usize = 2;
    let max_edit_distance: usize = 2;
    let mut best_match: Option<String> = None;
    let mut best_distance = usize::MAX;

    for (cmd_name, _) in COMMAND_MANAGER.read().get_commands() {
        if let Some(hamming_dist) = hamming_distance(target, cmd_name) {
            if hamming_dist <= max_hamming_distance && hamming_dist < best_distance {
                best_distance = hamming_dist;
                best_match = Some(String::from(cmd_name));
            }
        } else {
            let edit_dist = edit_distance(target, cmd_name);
            if edit_dist <= max_edit_distance && edit_dist < best_distance {
                best_distance = edit_dist;
                best_match = Some(String::from(cmd_name));
            }
        }
    }

    best_match
}

pub struct CommandManager {
    pub commands: AHashMap<String, Box<dyn Command>>,
    pub aliases: AHashMap<String, String>,
}

impl CommandManager {
    pub fn init() -> CommandManager {
        CommandManager {
            commands: AHashMap::new(),
            aliases: AHashMap::new(),
        }
    }

    pub fn get_commands(&self) -> std::collections::hash_map::Iter<'_, String, Box<dyn Command>> {
        self.commands.iter()
    }

    pub fn execute_command(
        &self,
        command: &str,
        args: Option<Vec<String>>,
    ) -> Result<String, ZenyxError> {
        if let Some(command) = self.commands.get(command) {
            let output = command.execute(args)?;
            Ok(output)
        } else {
            let corrected_cmd = check_similarity(command);
            if let Some(corrected_cmd) = corrected_cmd {
                println!(
                    "Command: {} was not found. Did you mean {}?",
                    command.red().bold(),
                    corrected_cmd.green().bold().italic()
                );
            }
            Err(ZenyxError::builder(ZenyxErrorKind::CommandExecution)
                .with_message(format!("Command '{}' not found.", command))
                .build())
        }
    }

    pub fn execute(&self, command: &str, args: Option<Vec<String>>) -> Result<String, ZenyxError> {
        match self.aliases.get(command) {
            Some(command) => self.execute(command, args),
            None => {
                let output = self.execute_command(command, args)?;
                Ok(output)
            }
        }
    }

    pub fn add_command(&mut self, command: Box<dyn Command>) {
        self.commands
            .insert(command.get_name().to_lowercase(), command);
    }

    pub fn add_alias(&mut self, alias: &str, command: &str) {
        self.aliases.insert(
            alias.to_string().to_lowercase(),
            command.to_string().to_lowercase(),
        );
    }
}

pub trait Command: Send + Sync {
    fn execute(&self, args: Option<Vec<String>>) -> Result<String, ZenyxError>;
    fn undo(&self);
    fn redo(&self);
    fn get_description(&self) -> String;
    fn get_name(&self) -> String;
    fn get_help(&self) -> String;
    fn get_params(&self) -> String;
}