use std::net::SocketAddr;
use std::sync::Arc;

use anyhow::{Result, anyhow};
use async_lock::Mutex;

use crate::connection::commands::CommandId;
use crate::connection::context::Context;

pub type CommandHandler =
    Box<dyn Fn(Arc<Mutex<Context>>, &[u8], SocketAddr) -> Result<()> + Send + Sync + 'static>;

pub struct CommandRouter {
    handlers: std::collections::HashMap<CommandId, CommandHandler>,
}

impl CommandRouter {
    pub fn new() -> Self {
        Self {
            handlers: std::collections::HashMap::new(),
        }
    }

    pub fn register<F>(&mut self, id: CommandId, handler: F) -> &mut Self
    where
        F: Fn(Arc<Mutex<Context>>, &[u8], SocketAddr) -> Result<()> + Send + Sync + 'static,
    {
        self.handlers.insert(id, Box::new(handler));
        self
    }

    pub fn dispatch(&self, ctx: Arc<Mutex<Context>>, data: &[u8], addr: SocketAddr) -> Result<()> {
        if data.len() < 4 {
            return Err(anyhow!("data too short"));
        }
        let cmd_id = CommandId::from_u32(u32::from_le_bytes([data[0], data[1], data[2], data[3]]))
            .ok_or_else(|| anyhow!("unknown command id"))?;

        if let Some(handler) = self.handlers.get(&cmd_id) {
            handler(ctx, &data[4..], addr)?;
        }
        Ok(())
    }
}

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