use std::collections::BTreeMap;
use std::convert::TryInto;

use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::{EntityGateway, GatewayError};
use crate::entity::item::*;

use std::sync::{Arc, Mutex};

#[derive(Clone)]
pub struct InMemoryGateway {
    users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
    user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
    characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
    items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
    inventories: Arc<Mutex<BTreeMap<CharacterEntityId, InventoryEntity>>>,
    banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
    equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
    mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
    weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
}

impl Default for InMemoryGateway {
    fn default() -> InMemoryGateway {
        InMemoryGateway {
            users: Arc::new(Mutex::new(BTreeMap::new())),
            user_settings: Arc::new(Mutex::new(BTreeMap::new())),
            characters: Arc::new(Mutex::new(BTreeMap::new())),
            items: Arc::new(Mutex::new(BTreeMap::new())),
            inventories: Arc::new(Mutex::new(BTreeMap::new())),
            banks: Arc::new(Mutex::new(BTreeMap::new())),
            equips: Arc::new(Mutex::new(BTreeMap::new())),
            mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
            weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
        }
    }
}

impl InMemoryGateway {
    fn apply_modifiers(&self, inventory: InventoryEntity ) -> InventoryEntity {
        let items = self.items.lock().unwrap();
        let inventory_items = inventory.items.into_iter()
            .map(|item| {
                item.map_individual(|mut item| {
                    item.item = match item.item {
                        ItemDetail::Weapon(mut weapon) => {
                            if let Some(weapon_modifiers) = self.weapon_modifiers.lock().unwrap().get(&item.id) {
                                for weapon_modifier in weapon_modifiers.iter() {
                                    weapon.apply_modifier(weapon_modifier);
                                }
                            }
                            ItemDetail::Weapon(weapon)
                        },
                        ItemDetail::Mag(mag) => {
                            let mut mag = mag::Mag::baby_mag(mag.color as u16);
                            if let Some(mag_modifiers) = self.mag_modifiers.lock().unwrap().get(&item.id) {
                                for mag_modifier in mag_modifiers.iter() {
                                    match mag_modifier {
                                        mag::MagModifier::FeedMag {food} => {
                                            if let Some(mag_feed) = items.get(food) {
                                                if let ItemDetail::Tool(mag_feed) = mag_feed.item {
                                                    mag.feed(mag_feed.tool)
                                                }
                                            }
                                        },
                                        mag::MagModifier::OwnerChange(class, section_id) => {
                                            mag.change_owner(*class, *section_id)
                                        },
                                        mag::MagModifier::MagCell(mag_cell_id) => {
                                            if let Some(mag_cell) = items.get(mag_cell_id) {
                                                if let ItemDetail::Tool(mag_cell) = mag_cell.item {
                                                    mag.apply_mag_cell(mag_cell.tool.try_into().unwrap())
                                                }
                                            }
                                        },
                                        _ => {}
                                    }
                                }
                            }
                            ItemDetail::Mag(mag)
                        }
                        _ => {
                            item.item
                        }
                    };

                    item
                })
            })
            .collect();
        InventoryEntity::new(inventory_items)
    }
}

#[async_trait::async_trait]
impl EntityGateway for InMemoryGateway {
    async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
        let mut users = self.users.lock().unwrap();
        let id = users
            .iter()
            .fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
            + 1;
        let user = UserAccountEntity {
            id: UserAccountId(id),
            username: user.username,
            password: user.password,
            guildcard: user.guildcard,
            team_id: user.team_id,
            banned_until: user.banned_until,
            muted_until: user.muted_until,
            created_at: chrono::Utc::now(),
            flags: user.flags,
            activated: user.activated,
            at_login: false,
            at_character: false,
            at_ship: false,
        };
        users.insert(user.id, user.clone());
        Ok(user)
    }

    async fn get_user_by_id(&self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
        let users = self.users.lock().unwrap();
        users.get(&id).cloned().ok_or(GatewayError::Error)
    }

    async fn get_user_by_name(&self, username: String) -> Result<UserAccountEntity, GatewayError> {
        let users = self.users.lock().unwrap();
        users
            .iter()
            .find(|(_, k)| k.username == username)
            .map(|(_, k)| k.clone())
            .ok_or(GatewayError::Error)
    }

    async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError>  {
        let mut users = self.users.lock().unwrap();
        users.insert(user.id, user.clone());
        Ok(())
    }

    async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result<UserSettingsEntity, GatewayError> {
        let mut user_settings = self.user_settings.lock().unwrap();
        let id = user_settings
            .iter()
            .fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
            + 1;
        let new_settings = UserSettingsEntity {
            id: UserSettingsId(id),
            user_id: settings.user_id,
            settings: settings.settings,
        };
        user_settings.insert(new_settings.id, new_settings.clone());
        Ok(new_settings)
    }

    async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
        let user_settings = self.user_settings.lock().unwrap();
        user_settings
            .iter()
            .find(|(_, k)| k.user_id == user.id)
            .map(|(_, k)| k.clone())
            .ok_or(GatewayError::Error)
    }

    async fn get_characters_by_user(&self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
        let characters = self.characters.lock().unwrap();
        const NONE: Option<CharacterEntity> = None;
        let mut chars = [NONE; 4];
        characters
            .iter()
            .filter(|(_, c)| c.user_id == user.id)
            .for_each(|(_, c)| chars[c.slot as usize] = Some(c.clone()));
        Ok(chars)
    }

    async fn create_character(&mut self, character: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
        let mut characters = self.characters.lock().unwrap();
        let id = characters
            .iter()
            .fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
            + 1;

        let new_character = CharacterEntity {
            id: CharacterEntityId(id),
            user_id: character.user_id,
            slot: character.slot,
            name: character.name,
            exp: character.exp,
            char_class: character.char_class,
            section_id: character.section_id,
            appearance: character.appearance,
            techs: character.techs,
            config: character.config,
            info_board: character.info_board,
            guildcard: character.guildcard,
            materials: character.materials,
            tech_menu: character.tech_menu,
            meseta: character.meseta,
            bank_meseta: character.bank_meseta,
            option_flags: character.option_flags,
        };
        characters.insert(new_character.id, new_character.clone());
        Ok(new_character)
    }

    async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError>  {
        let mut characters = self.characters.lock().unwrap();
        characters.insert(char.id, char.clone());
        Ok(())
    }

    async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
        Ok(GuildCardDataEntity::new(user.id))
    }

    async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
        let mut items = self.items.lock().unwrap();
        let id = items
            .iter()
            .fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
            + 1;
        let new_item = ItemEntity {
            id: ItemEntityId(id),
            item: item.item,
        };
        items.insert(ItemEntityId(id), new_item.clone());
        Ok(new_item)
    }

    async fn add_item_note(&mut self, item_id: &ItemEntityId, item_note: ItemNote) -> Result<(), GatewayError> {
        Ok(())
    }

    async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> {
        self.mag_modifiers.lock().unwrap()
            .entry(*mag_item_id)
            .or_insert_with(Vec::new)
            .push(mag::MagModifier::FeedMag {
                food: *tool_item_id
            });
        Ok(())
    }

    async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> {
        self.mag_modifiers.lock().unwrap()
            .entry(*mag_item_id)
            .or_insert_with(Vec::new)
            .push(mag::MagModifier::OwnerChange(character.char_class, character.section_id));
        Ok(())
    }

    async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> {
        self.mag_modifiers.lock().unwrap()
            .entry(*mag_item_id)
            .or_insert_with(Vec::new)
            .push(mag::MagModifier::MagCell(*mag_cell_id));
        Ok(())
    }

    async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> {
        self.weapon_modifiers.lock().unwrap()
            .entry(*item_id)
            .or_insert_with(Vec::new)
            .push(modifier);
        Ok(())
    }

    async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
        let inventories = self.inventories.lock().unwrap();
        Ok(inventories
           .iter()
           .find(|(id, _)| **id == *char_id)
           .map(|(_, inv)| inv.clone())
           .map(|inv| self.apply_modifiers(inv))
           .unwrap_or_default())
    }

    async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: BankName) -> Result<BankEntity, GatewayError> {
        let banks = self.banks.lock().unwrap();
        Ok(banks
           .iter()
           .find(|(id, _)| **id == *char_id)
           .map(|(_, b)| b.clone())
           .unwrap_or_default())
    }

    async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
        let mut inventories = self.inventories.lock().unwrap();
        inventories.insert(*char_id, inventory.clone());
        Ok(())
    }

    // TOOD: impl bank name
    async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, _bank_name: BankName) -> Result<(), GatewayError> {
        let mut banks = self.banks.lock().unwrap();
        banks.insert(*char_id, bank.clone());
        Ok(())
    }

    async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
        let equips = self.equips.lock().unwrap();
        Ok(equips
            .iter()
            .find(|(id, _)| **id == *char_id)
            .map(|(_, inv)| inv.clone())
            .unwrap_or_default())
    }

    async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equipped: &EquippedEntity) -> Result<(), GatewayError> {
        let mut equips = self.equips.lock().unwrap();
        equips.insert(*char_id, equipped.clone());
        Ok(())
    }

    async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, amount: usize) -> Result<(), GatewayError> {
        let mut characters = self.characters.lock().unwrap();
        if let Some(char) = characters.get_mut(&char_id) {
            char.meseta = amount as u32;
        }
        Ok(())
    }
}