use std::collections::HashMap;

use crate::{
    item::{AvatarItem, EquipItem, WeaponItem},
    listener::{NotifyListener, NotifyListenerExt},
};

mod avatar_unit;
mod enums;
mod property;

pub use avatar_unit::{EquipmentRepository, AvatarUnit};
use common::time_util;
use config::TemplateCollection;
pub use enums::*;
use yixuan_proto::{
    BigBossInfo, DoubleEliteInfo, DungeonQuestFinishedScNotify, common::LogBattleStatistics,
};

#[derive(Default, Clone)]
pub struct Dungeon {
    pub quest_id: u32,
    pub quest_type: u32,
    pub begin_time: i64,
    pub equipment: DungeonEquipment,
    pub avatar_units: Vec<AvatarUnit>,
    pub inner_quest_id_list: Vec<u32>,
    pub avatar_contribution: HashMap<u32, u32>,
    pub battle_statistic: LogBattleStatistics,
    pub pending_quest_finish_notifies: HashMap<u32, DungeonQuestFinishedScNotify>,
    pub big_boss_info: Option<BigBossInfo>,
    pub double_elite_info: Option<DoubleEliteInfo>,
}

#[derive(Default, Clone)]
pub struct DungeonEquipment {
    pub avatars: HashMap<u32, AvatarItem>,
    pub weapons: HashMap<u32, WeaponItem>,
    pub equips: HashMap<u32, EquipItem>,
}

impl Dungeon {
    pub fn new(quest_id: u32, quest_type: u32) -> Self {
        Self {
            quest_id,
            quest_type,
            begin_time: time_util::unix_timestamp_seconds(),
            equipment: DungeonEquipment::default(),
            avatar_units: Vec::new(),
            inner_quest_id_list: Vec::new(),
            avatar_contribution: HashMap::new(),
            battle_statistic: LogBattleStatistics::default(),
            pending_quest_finish_notifies: HashMap::new(),
            big_boss_info: None,
            double_elite_info: None,
        }
    }

    pub fn finish_main_dungeon_quest(&mut self, result: i32) {
        self.pending_quest_finish_notifies.insert(
            self.quest_id,
            DungeonQuestFinishedScNotify {
                quest_id: self.quest_id,
                result: if result == 1 { 1 } else { 0 },
                rank: if result == 1 { 4 } else { 0 },
                statistics: HashMap::from([(
                    EQuestStatisticsType::CostTime.into(),
                    (time_util::unix_timestamp_seconds() - self.begin_time) as u64,
                )]),
                avatar_contribution_list: self
                    .avatar_contribution
                    .iter()
                    .map(
                        |(&avatar_id, &contribution)| yixuan_proto::AvatarContributionInfo {
                            avatar_id,
                            contribution,
                        },
                    )
                    .collect(),
                ..Default::default()
            },
        );
    }

    pub fn flush_dungeon_quest_notifies(&mut self, listener: &mut dyn NotifyListener) {
        std::mem::take(&mut self.pending_quest_finish_notifies)
            .into_iter()
            .for_each(|(_, notify)| listener.add(notify));
    }

    pub fn update_statistic(&mut self, statistic: LogBattleStatistics) {
        statistic.avatar_list.iter().for_each(|avatar| {
            *self
                .avatar_contribution
                .entry(avatar.avatar_id as u32)
                .or_default() = avatar.damage as u32; // TODO: calculate contribution points properly
        });

        self.battle_statistic = statistic;
    }

    pub fn add_avatar(&mut self, avatar_id: u32, templates: &TemplateCollection) {
        self.avatar_units
            .push(AvatarUnit::new(avatar_id, templates, &self.equipment));
    }

    pub fn as_client_proto(&self) -> yixuan_proto::DungeonInfo {
        yixuan_proto::DungeonInfo {
            quest_id: self.quest_id,
            quest_type: self.quest_type,
            dungeon_equip_info: Some(yixuan_proto::DungeonEquipInfo {
                avatar_list: self
                    .equipment
                    .avatars
                    .values()
                    .map(AvatarItem::as_client_proto)
                    .collect(),
                weapon_list: self
                    .equipment
                    .weapons
                    .iter()
                    .map(|(&uid, weapon)| weapon.as_client_proto(uid))
                    .collect(),
                equip_list: self
                    .equipment
                    .equips
                    .iter()
                    .map(|(&uid, equip)| equip.as_client_proto(uid))
                    .collect(),
                buddy: Some(yixuan_proto::BuddyInfo::default()),
                ..Default::default()
            }),
            avatar_list: self
                .avatar_units
                .iter()
                .map(AvatarUnit::as_proto)
                .collect(),
            dungeon_quest_info: Some(yixuan_proto::DungeonQuestInfo {
                inner_quest_id_list: self.inner_quest_id_list.clone(),
            }),
            dungeon_statistics: Some(yixuan_proto::DungeonStatistics::default()),
            begin_time: self.begin_time,
            big_boss_info: self.big_boss_info.clone(),
            double_elite_info: self.double_elite_info.clone(),
        }
    }
}

impl EquipmentRepository for DungeonEquipment {
    fn get_avatar(&self, id: u32) -> Option<&AvatarItem> {
        self.avatars.get(&id)
    }

    fn get_weapon(&self, uid: u32) -> Option<&WeaponItem> {
        self.weapons.get(&uid)
    }

    fn get_equip(&self, uid: u32) -> Option<&EquipItem> {
        self.equips.get(&uid)
    }
}
