/*

ModulesManager is used to manage Modules and BotActions associated with those modules

pub struct ModulesManager {
    statusdb: HashMap<ModType,Vec<ModStatusType>>,
    botactions: HashMap<ModType,Vec<BotAction>>,
}

- statusdb: HashMap<ModType,Vec<ModStatusType>> - Defines Modules and their ModStatusType (e.g., Enabled at an Instance level, Disabled at a Channel Level)
- botactions: HashMap<ModType,Vec<BotAction>> - Defines Modules and their BotActions (e.g., BotCommand , Listener, Routine)

Example
    {
        ModulesManager {
            statusdb: {BotModule("experiments 004"): [Enabled(Instance)]},
            botactions: {BotModule("experiments 004"): [C(BotCommand { module: BotModule("experiments 004"), command: "DUPCMD4", alias: ["DUPALIAS4A", "DUPALIAS4B"], help: "DUPCMD4 tester" })]} }
    }

*/

use core::panic;

use std::collections::HashMap;
// use std::error::Error;
use std::sync::Arc;

use twitch_irc::message::PrivmsgMessage;

use tokio::sync::RwLock;

use async_trait::async_trait;

use self::bot_actions::actions_util::BotAR;
use crate::core::botinstance::{BotInstance, ChType,ChangeResult};
use crate::core::botlog;
use crate::core::identity;

use crate::core::bot_actions;
pub use ChType::Channel;
pub use ModType::BotModule;

// use super::identity::ChangeResult;


#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum ModType {
    BotModule(String),
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum ModGroup {
    Core,
    Custom,
}

#[derive(Debug)]
pub enum StatusLvl {
    Instance,
    _Ch(ChType),
}

#[derive(Debug)]
pub enum StatusType {
    Enabled(StatusLvl),
    Disabled(StatusLvl),
}

pub enum BotAction {
    C(BotCommand),
    L(Listener),
    R(Routine),
}

impl BotAction {
    pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) {
        match self {
            BotAction::L(a) => a.execute(m, n).await,
            BotAction::C(a) => a.execute(m, n).await,
            _ => (),
        }
    }
}

#[async_trait]
pub trait BotActionTrait {
    async fn add_to_bot(self, bot: BotInstance);
    async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>);
    async fn add_core_to_bot(self, bot: BotInstance);
    async fn add_core_to_modmgr(self, modmgr: Arc<ModulesManager>);
}

pub struct BotCommand {
    pub module: ModType,
    pub command: String,    // command call name
    pub alias: Vec<String>, // String of alternative names
    pub exec_body: bot_actions::actions_util::ExecBody,
    pub help: String,
    pub required_roles: Vec<identity::UserRole>,
}

impl BotCommand {
    pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) {
        (*self.exec_body)(m, n).await;
    }
}

#[async_trait]
impl BotActionTrait for BotCommand {
    async fn add_to_bot(self, bot: BotInstance) {
        self.add_to_modmgr(bot.botmodules).await;
    }

    async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>) {
        modmgr
            .add_botaction(self.module.clone(), BotAction::C(self))
            .await
    }

    async fn add_core_to_bot(self, bot: BotInstance) {
        self.add_core_to_modmgr(bot.botmodules).await;
    }

    async fn add_core_to_modmgr(self, modmgr: Arc<ModulesManager>) {
        modmgr
            .add_core_act(self.module.clone(), BotAction::C(self))
            .await
    }
}

pub struct Listener {
    pub module: ModType,
    pub name: String,
    pub exec_body: bot_actions::actions_util::ExecBody,
    pub help: String,
}

impl Listener {
    pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) {
        (self.exec_body)(m, n).await;
    }
}

#[async_trait]
impl BotActionTrait for Listener {
    async fn add_to_bot(self, bot: BotInstance) {
        botlog::trace(
            "Adding action to bot",
            Some("BotModules > BotActionTrait > add_to_bot()".to_string()),
            None,
        );
        self.add_to_modmgr(bot.botmodules).await;
    }

    async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>) {
        botlog::trace(
            "Adding action to module manager",
            Some("BotModules > BotActionTrait > add_to_bot()".to_string()),
            None,
        );

        modmgr
            .add_botaction(self.module.clone(), BotAction::L(self))
            .await;
    }

    async fn add_core_to_bot(self, bot: BotInstance) {
        self.add_core_to_modmgr(bot.botmodules).await;
    }

    async fn add_core_to_modmgr(self, modmgr: Arc<ModulesManager>) {
        modmgr
            .add_core_act(self.module.clone(), BotAction::L(self))
            .await
    }
}

#[derive(Debug)]
pub struct Routine {}

pub struct ModulesManager {
    // statusdb: Arc<RwLock<HashMap<ModType, Vec<ModStatusType>>>>,
    statusdb: Arc<RwLock<HashMap<(ModType,ModGroup), Vec<StatusType>>>>,
    pub botactions: Arc<RwLock<HashMap<ModType, Vec<BotAction>>>>,
}

/*

statusdb
    <HashMap
        <ModType, <-- e.g., BotModule(String::from("experiments001"))
        Vec<ModStatusType> <-- shows Enabled/Disabled per Status level

botactions
    HashMap<
        ModType, <-- e.g.,  BotModule(String::from("experiments001"))
        Vec<BotAction>> BotCommand, Listener

*/

impl ModulesManager {
    pub async fn init() -> Arc<ModulesManager> {
        let mgr = ModulesManager {
            statusdb: Arc::new(RwLock::new(HashMap::new())),
            botactions: Arc::new(RwLock::new(HashMap::new())),
        };

        // :: [x] initialize core modules
        botlog::trace(
            "ModulesManager > init() > Adding modules",
            Some("ModulesManager > init()".to_string()),
            None,
        );

        let mgrarc = Arc::new(mgr);

        // 1. load core modules
        crate::core::identity::init(Arc::clone(&mgrarc)).await;

        // 2. load custom modules
        crate::custom::init(Arc::clone(&mgrarc)).await;

        botlog::trace(
            ">> Modules Manager : End of Init",
            Some("ModulesManager > init()".to_string()),
            None,
        );

        mgrarc
    }

    pub fn modstatus(&self, _: ModType, _: ChType) -> StatusType {
        // Example usage : botmanager.modstatus(
        //     BotModule("GambaCore"),
        //     Channel("modulatingforce")
        // )
        // - The ModStatusType checks in the context of the given channel ,
        //  but also validates based on wheher the module is disabled at a bot instance
        //  level as well
        StatusType::Enabled(StatusLvl::Instance)
    }

    // pub fn togglestatus(&self, _: ModType, _: ChType) -> StatusType {
    //     // enables or disables based on current status
    //     StatusType::Enabled(StatusLvl::Instance)
    // }

    // pub fn setstatus(&self, _: ModType, _: StatusType) -> Result<&str, Box<dyn Error>> {
    //     // sets the status based given ModSatusType
    //     // e.g., b.setstatus(BodModule("GambaCore"), Enabled(Channel("modulatingforce"))).expect("ERROR")
    //     Ok("")
    // }

    pub fn set_instance_disabled(&self, _in_module: ModType) -> (StatusType,ChangeResult) {
        // at Instance level
        // - If core module, do nothing
        (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string()))
    }

    pub fn force_disabled(&self, _in_module: ModType) -> (StatusType,ChangeResult) {
        // Disables the module at Instance level, and removes all Enabled/Disabled at Channel level
        //  - Bot Moderators MUST Re-enable if they were enabled before
        //  - If core module, do nothing
        (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string()))
    }

    pub fn set_instance_enabled(&self, _in_module: ModType) -> (StatusType,ChangeResult) {
        // at Instance level
        //  - If core module, do nothing
        (StatusType::Enabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string()))
    }

    pub fn set_ch_disabled(&self, _in_module: ModType , _in_chnl: ChType) -> (StatusType,ChangeResult) {
        // at Instance level
        //  - If core module, do nothing
        (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string()))
    }

    pub fn set_ch_enabled(&self, _in_module: ModType , _in_chnl: ChType) -> (StatusType,ChangeResult) {
        // at Instance level
        //  - If core module, do nothing
        (StatusType::Enabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string()))
    }



    pub async fn add_botaction(&self, in_module: ModType, in_action: BotAction) {
        self.int_add_botaction(in_module,ModGroup::Custom,in_action).await;
    }

    pub async fn add_core_act(&self, in_module: ModType, in_action: BotAction) {
        self.int_add_botaction(in_module,ModGroup::Core,in_action).await;
    }

    async fn int_add_botaction(&self, in_module: ModType, in_modgroup: ModGroup, in_action: BotAction) {
        botlog::trace(
            "Add botaction called",
            Some("ModulesManager > init()".to_string()),
            None,
        );

        /*
        adds a BotAction to the Modules Manager - This will require a BotModule passed as well
        This will including the logic of a valid add
        If it fails to add, either a PANIC or some default coded business rules that handles the botaction add
        For example, this Should PANIC (ideally Panic?) if it does not successfully add a bot module
        -- Being unable to indicates a Programming/Developer code logic issue : They cannot add botactions that already exists (?)
        -- In particular to BotCommands, which must have Unique command call names and aliases that to not conflict with any other
               already BotCommand added name or alias
        Other types might be fine? For example, if 2 modules have their own listeners but each have the name "targetchatter" ,
          both would be called separately, even if they both have the same or different logic
        */

        // [x] Before Adding, validate the following :
        //    - If BotAction to Add is a BotCommand , In Module Manager DB (botactions),
        //       Check All Other BotAction Command Names & Aliases to ensure they don't conflict

        async fn find_conflict_module(mgr: &ModulesManager, act: &BotAction) -> Option<ModType> {
            if let BotAction::C(incmd) = act {
                let actdb = mgr.botactions.read().await;

                for (module, moduleactions) in &(*actdb) {
                    for modact in moduleactions.iter() {
                        if let BotAction::C(dbcmd) = &modact {
                            // At this point, there is an command incmd and looked up dbcmd

                            // [x] check if given botcommand c.command:String conflicts with any in botactions

                            if incmd.command.to_lowercase() == dbcmd.command.to_lowercase() {
                                // Returning State - with the identified module
                                return Some(module.clone()); // works
                            }

                            for a in &dbcmd.alias {
                                if incmd.command.to_lowercase() == a.to_lowercase() {
                                    // Returning State - with the identified module

                                    return Some(module.clone()); // works
                                }
                            }

                            // [x] Then do the same check except for each c.alias

                            for inalias in &incmd.alias {
                                if inalias.to_lowercase() == dbcmd.command.to_lowercase() {
                                    // Returning State - with the identified module

                                    return Some(module.clone()); // works
                                }

                                for a in &dbcmd.alias {
                                    if inalias.to_lowercase() == a.to_lowercase() {
                                        // Returning State - with the identified module

                                        return Some(module.clone()); // works
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // for all other scenarios (e.g., Listener, Routine), find no conflicts
            None
        }

        if let Some(c) = find_conflict_module(self, &in_action).await {
            panic!(
                "ERROR: Could not add module; there was a conflict with existing module {:?}",
                c
            )
        }

        let mut dbt = self.statusdb.write().await;
        // 
        let statusvector = dbt.entry((in_module.clone(),in_modgroup.clone())).or_insert(Vec::new());

        match in_modgroup {
            ModGroup::Core => statusvector.push(StatusType::Enabled(StatusLvl::Instance)) , // Pushes the Module as Enabled at Instance Level
            ModGroup::Custom => statusvector.push(StatusType::Disabled(StatusLvl::Instance)),
        }

        // statusvector.push(ModStatusType::Enabled(StatusLvl::Instance)); // Pushes the Module as Enabled at Instance Level

        let mut a = self.botactions.write().await;
        let modactions = a.entry(in_module.clone()).or_insert(Vec::new());

        modactions.push(in_action);

        botlog::trace(
            format!(
                "Modules Manager> add_botaction called - botactions size : {}",
                modactions.len()
            )
            .as_str(),
            Some("ModulesManager > init()".to_string()),
            None,
        );
    }

    fn _statuscleanup(&self, _: Option<ChType>) {
        // internal cleans up statusdb . For example :
        // - remove redudancies . If we see several Enabled("m"), only keep 1x
        // - Clarify Conflict. If we see Enabled("m") and Disabled("m") , we remove Enabled("m") and keep Disabled("m")
        // the IDEAL is that this is ran before every read/update operation to ensure quality
        // Option<ChType> can pass Some(Channel("m")) (as an example) so statuscleanup only works on the given channel
        // Passing None to chnl may be a heavy operation, as this will review and look at the whole table
    }
}