use std::{
    collections::{HashMap, hash_map},
    fmt::Debug,
    marker::PhantomData,
    cell::RefMut,
};

use crate::{base_objects::player, resource::Resource, IntoVoidResult, VoidResult, SomeResult};
use altv_sdk::ffi as sdk;
use anyhow::bail;

pub fn emit(event_name: impl ToString, args: mvalue::DynMValueArgs) -> VoidResult {
    unsafe { sdk::trigger_local_event(event_name.to_string(), mvalue::serialize_args(args)?) };
    Ok(())
}

pub type EventArgs<'a> = &'a Vec<mvalue::ConstMValue>;
pub type HandlerId = u64;
type EventName = String;

#[derive(Debug)]
pub struct LocalEventContext<'a> {
    pub args: EventArgs<'a>,
}

#[derive(Debug)]
pub struct PlayerEventContext<'a> {
    pub player: player::PlayerContainer,
    pub args: EventArgs<'a>,
}

pub type LocalEventHandler = Box<dyn FnMut(&LocalEventContext) -> VoidResult>;
pub type ClientEventHandler = Box<dyn FnMut(&PlayerEventContext) -> VoidResult>;

pub trait ScriptEventManager {
    type Handler;

    fn handlers(&self) -> &HashMap<EventName, HashMap<HandlerId, Self::Handler>>;
    fn handlers_mut(&mut self) -> &mut HashMap<EventName, HashMap<HandlerId, Self::Handler>>;

    fn next_handler_id(&mut self) -> HandlerId;

    fn is_event_handled(&self, event_name: &str) -> bool {
        self.handlers().contains_key(event_name)
    }

    fn add_handler(&mut self, event_name: EventName, handler: Self::Handler) -> HandlerId {
        let handler_id = self.next_handler_id();

        self.handlers_mut()
            .entry(event_name)
            .or_insert(HashMap::new())
            .insert(handler_id, handler);

        handler_id
    }

    fn remove_handler(&mut self, event_name: &str, handler_id: HandlerId) -> VoidResult {
        let handlers = self.handlers_mut().get_mut(event_name);
        let Some(handlers) = handlers else {
            bail!("No handlers for event: {event_name}")
        };

        let Some(_) = handlers.remove(&handler_id) else {
            bail!("Handler was not found in the map")
        };

        if !handlers.is_empty() {
            return Ok(());
        }

        let Some(_) = self.handlers_mut().remove(event_name) else {
            bail!("Handlers map of event: {event_name} was not found in the map (handlers map should be removed because its empty)")
        };

        Ok(())
    }

    fn get_handlers_for_event(
        &mut self,
        event_name: &str,
    ) -> Option<hash_map::ValuesMut<HandlerId, Self::Handler>> {
        self.handlers_mut()
            .get_mut(event_name)
            .map(|v| v.values_mut())
    }

    fn remove_scheduled_handlers<T>(&mut self, mut schedule: RefMut<EventSchedule<T>>) {
        for (event_name, id) in schedule.to_be_destroyed.iter() {
            let result = self.remove_handler(event_name, *id);

            match result {
                Ok(_) => {
                    logger::debug!(
                        "script event handler of event: \"{event_name}\" removed successfully"
                    );
                }
                Err(e) => {
                    logger::error!("Failed to remove script event handler (local or client) of event: \"{event_name}\" with id: {id} (error: {e})");
                }
            }
        }
        schedule.to_be_destroyed.clear();
    }
}

#[derive(Default)]
pub struct LocalEventManager {
    handlers: HashMap<EventName, HashMap<HandlerId, LocalEventHandler>>,
    handler_id: HandlerId,
}

impl LocalEventManager {
    pub fn init() {
        use crate::events;

        events::add_sdk_handler(events::SDKHandler::ServerScriptEvent(Box::new(|c| {
            let events::sdk_contexts::ServerScriptEvent { name, args } = c;

            Resource::with_local_script_events_mut(|mut events, resource| {
                if !events.is_event_handled(name) {
                    logger::debug!("local event is unhandled: {name}");
                    return;
                }
                events.handle_event(
                    resource.local_script_events_schedule.borrow_mut(),
                    name,
                    args,
                );
            });

            Ok(())
        })));
    }

    fn handle_event(
        &mut self,
        schedule: RefMut<LocalEventSchedule>,
        event_name: &str,
        args: EventArgs,
    ) {
        self.remove_scheduled_handlers(schedule);

        if let Some(handlers) = self.get_handlers_for_event(event_name) {
            let context = LocalEventContext { args };
            for h in handlers {
                if let Err(error) = h(&context) {
                    logger::error!("handler of event: {event_name:?} failed with error: {error:?}");
                } else {
                    logger::debug!("handler of event: {event_name:?} called successfully");
                }
            }
        } else {
            logger::debug!("handle_event no handlers for local event: {event_name:?}")
        }
    }
}

impl ScriptEventManager for LocalEventManager {
    type Handler = LocalEventHandler;

    fn handlers(&self) -> &HashMap<EventName, HashMap<HandlerId, Self::Handler>> {
        &self.handlers
    }

    fn handlers_mut(&mut self) -> &mut HashMap<EventName, HashMap<HandlerId, Self::Handler>> {
        &mut self.handlers
    }

    fn next_handler_id(&mut self) -> HandlerId {
        self.handler_id += 1;
        self.handler_id
    }
}

impl Debug for LocalEventManager {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "LocalEventManager{{todo}}")
    }
}

#[derive(Default)]
pub struct ClientEventManager {
    handlers: HashMap<EventName, HashMap<HandlerId, ClientEventHandler>>,
    handler_id: HandlerId,
}

impl ClientEventManager {
    pub fn init() {
        use crate::events;

        events::add_sdk_handler(events::SDKHandler::ClientScriptEvent(Box::new(|c| {
            let events::sdk_contexts::ClientScriptEvent { name, player, args } = c;

            Resource::with_client_script_events_mut(|mut events, resource| {
                if !events.is_event_handled(name) {
                    logger::debug!("client event is unhandled: {name}");
                    return;
                }
                events.handle_event(
                    resource.client_script_events_schedule.borrow_mut(),
                    name,
                    player.clone(),
                    args,
                );
            });

            Ok(())
        })));
    }

    pub fn handle_event(
        &mut self,
        schedule: RefMut<ClientEventSchedule>,
        event_name: &str,
        player: player::PlayerContainer,
        args: EventArgs,
    ) {
        self.remove_scheduled_handlers(schedule);

        if let Some(handlers) = self.get_handlers_for_event(event_name) {
            let context = PlayerEventContext { player, args };
            for h in handlers {
                if let Err(error) = h(&context) {
                    logger::error!(
                        "handler of client event: {event_name:?} failed with error: {error:?}"
                    );
                } else {
                    logger::debug!("handler of client event: {event_name:?} called successfully");
                }
            }
        } else {
            logger::debug!("handle_event no handlers for client event: {event_name:?}")
        }
    }
}

impl ScriptEventManager for ClientEventManager {
    type Handler = ClientEventHandler;

    fn handlers(&self) -> &HashMap<EventName, HashMap<HandlerId, Self::Handler>> {
        &self.handlers
    }

    fn handlers_mut(&mut self) -> &mut HashMap<EventName, HashMap<HandlerId, Self::Handler>> {
        &mut self.handlers
    }

    fn next_handler_id(&mut self) -> HandlerId {
        self.handler_id += 1;
        self.handler_id
    }
}

impl Debug for ClientEventManager {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "ClientEventManager {{todo}} ")
    }
}

pub fn on<V: IntoVoidResult>(
    event_name: impl ToString,
    mut handler: impl FnMut(&LocalEventContext) -> V + 'static,
) -> LocalEventController {
    let event_name = event_name.to_string();

    let id = Resource::with_local_script_events_mut(|mut local_events, _| {
        local_events.add_handler(
            event_name.clone(),
            Box::new(move |c| handler(c).into_void_result()),
        )
    });

    LocalEventController::new(event_name, id)
}

pub fn on_player<V: IntoVoidResult>(
    event_name: impl ToString,
    mut handler: impl FnMut(&PlayerEventContext) -> V + 'static,
) -> PlayerEventController {
    let event_name = event_name.to_string();

    let id = Resource::with_client_script_events_mut(|mut local_events, _| {
        local_events.add_handler(
            event_name.clone(),
            Box::new(move |c| handler(c).into_void_result()),
        )
    });

    PlayerEventController::new(event_name, id)
}

pub mod script_event_type {
    #[derive(Debug, Default)]
    pub struct Local;
    #[derive(Debug, Default)]
    pub struct Client;
}

#[derive(Debug)]
pub struct ScriptEventController<T> {
    event_name: Option<EventName>,
    id: Option<HandlerId>,
    __type: PhantomData<T>,
}

impl<T> ScriptEventController<T> {
    fn new(event_name: EventName, id: HandlerId) -> Self {
        Self {
            event_name: Some(event_name),
            id: Some(id),
            __type: PhantomData,
        }
    }

    fn take_data(&mut self) -> SomeResult<(EventName, HandlerId)> {
        let (Some(id), Some(event_name)) = (self.id.take(), self.event_name.take()) else {
            bail!("Already destroyed")
        };
        Ok((event_name, id))
    }
}

pub type LocalEventController = ScriptEventController<script_event_type::Local>;

impl LocalEventController {
    pub fn destroy(&mut self) -> VoidResult {
        let (event_name, id) = self.take_data()?;

        Resource::with_local_script_events_schedule_mut(|mut v, _| {
            v.add_to_be_destroyed(event_name, id)
        });

        Ok(())
    }
}

pub type PlayerEventController = ScriptEventController<script_event_type::Client>;

impl PlayerEventController {
    pub fn destroy(&mut self) -> VoidResult {
        let (event_name, id) = self.take_data()?;

        Resource::with_client_script_events_schedule_mut(|mut v, _| {
            v.add_to_be_destroyed(event_name, id)
        });

        Ok(())
    }
}

#[derive(Default, Debug)]
pub struct EventSchedule<T> {
    to_be_destroyed: Vec<(EventName, HandlerId)>,
    __type: PhantomData<T>,
}

impl<T> EventSchedule<T> {
    fn add_to_be_destroyed(&mut self, event_name: EventName, handler_id: HandlerId) {
        self.to_be_destroyed.push((event_name, handler_id));
    }
}

pub type LocalEventSchedule = EventSchedule<script_event_type::Local>;
pub type ClientEventSchedule = EventSchedule<script_event_type::Client>;
