use std::{
    cell::RefCell,
    collections::{BTreeMap, HashSet},
    rc::{Rc, Weak},
};

use proto::{
    AbilityControlBlock, AbilitySyncStateInfo, Retcode, SceneAvatarInfo, SceneEntityInfo,
    SceneTeamUpdateNotify,
};

use crate::{
    creature::{Creature, LifeState},
    player::avatar::Avatar,
};
use crate::{gadget::WeaponGadget, player::Player};

use super::Scene;

#[derive(Clone)]
pub struct SceneTeamAvatar {
    pub uid: u32,
    pub avatar_guid: u64,
    pub avatar_ptr: Rc<RefCell<Avatar>>,
}

impl std::fmt::Debug for SceneTeamAvatar {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SceneTeamAvatar")
            .field("uid", &self.uid)
            .field("avatar_guid", &self.avatar_guid)
            .finish()
    }
}

impl SceneTeamAvatar {
    pub fn to_client(&self, proto: &mut proto::SceneTeamAvatar, is_mp_mode: bool) {
        use crate::entity::Entity;

        let avatar = self.avatar_ptr.borrow();

        proto.player_uid = self.uid;
        proto.avatar_guid = self.avatar_guid;
        proto.entity_id = avatar.entity_id();
        proto.is_on_scene = avatar.is_on_scene();
        proto.is_player_cur_avatar = true; // TODO!
        proto.avatar_ability_info = Some(AbilitySyncStateInfo::default());
        proto.weapon_ability_info = Some(AbilitySyncStateInfo::default());

        let mut scene_entity_info = SceneEntityInfo::default();
        avatar.to_client_scene_entity(&mut scene_entity_info);
        proto.scene_entity_info = Some(scene_entity_info);

        // TODO: AbilityComp::get_proto_ability_control_block()
        proto.ability_control_block = Some(AbilityControlBlock::default());
        avatar
            .base_avatar()
            .ability_comp
            .borrow()
            .get_proto_ability_control_block(&mut proto.ability_control_block.as_mut().unwrap());

        {
            let weapon_gadget = avatar
                .base_avatar()
                .equip_comp
                .weapon_gadget
                .as_ref()
                .unwrap();

            proto.weapon_guid = match &*weapon_gadget.borrow() {
                WeaponGadget::Avatar(gadget) => gadget.weapon_guid,
            };
            proto.weapon_entity_id = weapon_gadget.borrow().entity_id();
        }

        if is_mp_mode {
            proto.avatar_info = Some(avatar.to_client());

            let mut scene_avatar_info = SceneAvatarInfo::default();
            avatar.to_client_avatar_entity(&mut scene_avatar_info);
            proto.scene_avatar_info = Some(scene_avatar_info);
        }
    }
}

#[derive(Default)]
pub struct SceneTeam {
    pub player_uid_vec: Vec<u32>,
    pub player_map: BTreeMap<u32, Weak<Player>>,
    pub team_avatar_map: BTreeMap<u32, Vec<SceneTeamAvatar>>,
    pub is_in_mp_mode: bool,
    pub host_uid: u32,
    pub scene_id: u32,
    pub is_in_single_avatar_mode: bool,
}

impl SceneTeam {
    pub fn init(&mut self, player: Rc<Player>, host_uid: u32, scene_id: u32) {
        self.host_uid = host_uid;
        self.scene_id = scene_id;
        self.player_map
            .insert(self.host_uid, Rc::downgrade(&player));
    }

    pub fn get_avatar_guid_vec(&self, uid: u32) -> Option<Vec<u64>> {
        let Some(avatar_vec) = self.team_avatar_map.get(&uid) else {
            return None;
        };

        Some(avatar_vec.iter().map(|a| a.avatar_guid).collect())
    }

    pub fn get_allow_avatar_num(&self, uid: u32) -> usize {
        if !self.team_avatar_map.contains_key(&uid) {
            return 0;
        }

        if self.is_in_single_avatar_mode {
            return 1;
        }

        let is_host = self.host_uid == uid;
        let team_player_num = self.get_team_player_num();

        Self::allow_avatar_num(team_player_num, is_host)
    }

    pub fn on_player_enter(&mut self, player: Rc<Player>) {
        if !self.team_avatar_map.contains_key(&player.uid) {
            if player.uid == self.host_uid {
                if self.player_uid_vec.len() != 0 {
                    tracing::info!("[SCENE_TEAM] host enter scene team after other, host_uid: {} player_uid_vec:{:?}", self.host_uid, self.player_uid_vec);
                }

                self.player_uid_vec.insert(0, player.uid);
            } else {
                self.player_uid_vec.push(player.uid);
            }

            self.team_avatar_map.insert(player.uid, Vec::new());
            self.player_map.insert(player.uid, Rc::downgrade(&player));
        }
    }

    pub fn set_player_avatar_team_and_add_to_scene(
        &mut self,
        uid: u32,
        mut avatar_guid_vec: Vec<u64>,
        appear_avatar_guid: u64,
        scene: &mut Scene,
        is_notify: bool,
    ) -> Result<(), Retcode> {
        if self
            .set_player_avatar_team(uid, &mut avatar_guid_vec, appear_avatar_guid, is_notify)
            .is_err()
        {
            tracing::warn!(
                "set_player_avatar_team failed, uid: {uid}, avatar_vec: {avatar_guid_vec:?}"
            );
            return Err(Retcode::RetFail);
        }

        for avatar in self.get_all_scene_team_avatar_vec() {
            if let Err(ret) = scene.add_avatar_and_weapon_entity(avatar.avatar_ptr.clone(), true) {
                tracing::error!("add_avatar_and_weapon_entity fails {avatar:?}");
                return Err(ret);
            }
        }

        if is_notify {
            self.notify_scene_team_update();
        }

        Ok(())
    }

    pub fn set_player_avatar_team(
        &mut self,
        uid: u32,
        guid_vec: &mut Vec<u64>,
        appear_avatar_guid: u64,
        _is_notify: bool,
    ) -> Result<(), Retcode> {
        let player = self
            .player_map
            .get(&uid)
            .map(|p| p.upgrade())
            .flatten()
            .ok_or(Retcode::RetFail)?;

        let allow_avatar_num = self.get_allow_avatar_num(uid);
        if allow_avatar_num < guid_vec.len() {
            tracing::warn!(
                "guid_vec size is {}, allow: {allow_avatar_num}",
                guid_vec.len()
            );
            guid_vec.truncate(allow_avatar_num);
        }

        if guid_vec.is_empty() {
            guid_vec.push(appear_avatar_guid);
        }

        let old_avatar_vec = self.get_all_scene_team_avatar_vec();
        let Some(player_avatar_vec) = self.team_avatar_map.get_mut(&uid) else {
            tracing::error!("set_player_avatar_team: uid: {uid} not found");
            return Err(Retcode::RetFail);
        };

        let mut new_avatar_vec = Vec::new();
        let mut avatar_guid_set = HashSet::with_capacity(guid_vec.len());

        for (i, guid) in guid_vec.iter().enumerate() {
            let avatar_comp = player.player_avatar_comp.borrow();
            let Some(avatar) = avatar_comp.find_avatar(*guid) else {
                tracing::error!("invalid avatar guid in guid_vec");
                return Err(Retcode::RetFail);
            };

            if !avatar_guid_set.insert(*guid) {
                tracing::error!(
                    "duplicate guid in guid_vec, guid:{}, guid_vec:{:?}",
                    *guid,
                    &guid_vec
                );
            }

            if i == 0 {
                if avatar.borrow().get_life_state() == LifeState::LifeDead {
                    tracing::warn!("avatar dead, guid:{}, guid_vec:{:?}", *guid, &guid_vec);
                }
            }

            new_avatar_vec.push(SceneTeamAvatar {
                uid,
                avatar_guid: *guid,
                avatar_ptr: avatar.clone(),
            });
        }

        *player_avatar_vec = new_avatar_vec;

        self.resize_avatar_team();
        self.on_scene_team_change(old_avatar_vec);

        Ok(())
    }

    pub fn notify_scene_team_update(&self) {
        let mut notify = SceneTeamUpdateNotify::default();

        for uid in &self.player_uid_vec {
            let Some(team_avatars) = self.team_avatar_map.get(&uid) else {
                continue;
            };

            let Some(player) = self.player_map.get(&uid).map(|p| p.upgrade()).flatten() else {
                continue;
            };

            for scene_team_avatar in team_avatars {
                let mut scene_team_avatar_proto = proto::SceneTeamAvatar::default();
                scene_team_avatar.to_client(&mut scene_team_avatar_proto, self.is_in_mp_mode);
                scene_team_avatar_proto.scene_id = self.scene_id;
                scene_team_avatar_proto.is_player_cur_avatar = player
                    .player_avatar_comp
                    .borrow()
                    .get_cur_avatar()
                    .unwrap()
                    .borrow()
                    .base_avatar()
                    .guid
                    == scene_team_avatar.avatar_guid;

                notify.scene_team_avatar_list.push(scene_team_avatar_proto);

                tracing::info!(
                    "notify avatar: {}",
                    scene_team_avatar
                        .avatar_ptr
                        .borrow()
                        .base_avatar()
                        .avatar_id
                );
            }
        }

        notify.is_in_mp = self.is_in_mp_mode;

        for (_, player_wtr) in self.player_map.iter() {
            if let Some(player) = player_wtr.upgrade() {
                player.send_proto(notify.clone());
            }
        }
    }

    pub fn get_all_scene_team_avatar_vec(&self) -> Vec<SceneTeamAvatar> {
        let mut retstr = Vec::new();
        for (_, avatar_vec) in self.team_avatar_map.iter() {
            avatar_vec.iter().for_each(|a| retstr.push(a.clone()));
        }

        retstr
    }

    pub fn get_team_player_num(&self) -> usize {
        self.player_map.len()
    }

    pub fn is_avatar_in_team(&self, player_uid: u32, avatar_guid: u64) -> bool {
        let Some(team_avatar_vec) = self.team_avatar_map.get(&player_uid) else {
            return false;
        };

        team_avatar_vec.iter().any(|a| a.avatar_guid == avatar_guid)
    }

    fn on_scene_team_change(&mut self, old_avatar_vec: Vec<SceneTeamAvatar>) {
        // TODO: RefreshAbilityGuard, FightPropGuard, TalentComp

        let new_avatar_vec = self.get_all_scene_team_avatar_vec();

        let mut diff_remove_avatar_vec = Vec::new();
        let mut diff_add_avatar_vec = Vec::new();

        for avatar in &old_avatar_vec {
            if !self.is_avatar_in_team(avatar.uid, avatar.avatar_guid) {
                diff_remove_avatar_vec.push(avatar.clone());
            }
        }

        for avatar in &new_avatar_vec {
            if !old_avatar_vec
                .iter()
                .any(|a| avatar.uid == a.uid && avatar.avatar_guid == a.avatar_guid)
            {
                diff_add_avatar_vec.push(avatar.clone());
            }
        }

        tracing::info!("[SCENE_TEAM_CHANGE] diff_remove_avatar_vec:{diff_remove_avatar_vec:?} diff_add_avatar_vec:{diff_add_avatar_vec:?}");

        for team_avatar in diff_remove_avatar_vec {
            team_avatar
                .avatar_ptr
                .borrow_mut()
                .on_remove_from_scene_team();
        }

        for team_avatar in diff_add_avatar_vec {
            team_avatar.avatar_ptr.borrow_mut().on_add_to_scene_team();
        }

        self.refresh_team_resonances();
    }

    fn refresh_team_resonances(&self) {
        // TODO: TeamResonanceExcelConfigMgr::getSatisfiedTeamResonances
    }

    fn resize_avatar_team(&mut self) {
        // TODO! This is probably only useful for MP,
        // so this is not implemented for now
    }

    fn allow_avatar_num(team_size: usize, is_host: bool) -> usize {
        match team_size {
            1 => 10,
            2 => 2,
            4 => 1,
            3 if is_host => 2,
            3 => 1,
            _ => 0,
        }
    }
}
