use std::{
    cell::{Cell, RefCell},
    rc::{Rc, Weak},
    sync::{self, mpsc::Receiver, Arc},
};

use common::{net::tools::Packet, tools::time_utils};
use data::{
    get_default_world_main_scene_id,
    math_def::Vector3,
    prop_type::{
        PROP_CUR_PERSIST_STAMINA, PROP_IS_FLYABLE, PROP_IS_GAME_TIME_LOCKED,
        PROP_IS_SPRING_AUTO_USE, PROP_IS_TRANSFERABLE, PROP_IS_WEATHER_LOCKED, PROP_MAX_STAMINA,
        PROP_PLAYER_EXP, PROP_PLAYER_HCOIN, PROP_PLAYER_LEVEL, PROP_PLAYER_SCOIN,
        PROP_SPRING_AUTO_USE_PERCENT,
    },
    AvatarUseType,
};

use prost::Message;
use proto::{
    AbilitySyncStateInfo, AvatarEnterSceneInfo, CmdID, DoSetPlayerBornDataNotify,
    EnterSceneDoneReq, EnterSceneDoneRsp, EnterScenePeerNotify, EnterSceneReadyReq, EnterType,
    MpLevelEntityInfo, PacketHead, PingReq, PingRsp, PlayerData, PlayerDataBin, PlayerDataNotify,
    PlayerEnterSceneInfoNotify, PlayerEnterSceneNotify, PlayerLoginRsp, PostEnterSceneReq,
    PostEnterSceneRsp, ResVersionConfig, Retcode, SavePlayerDataReq, SceneInitFinishReq,
    SetPlayerBornDataReq, TeamEnterSceneInfo, VisionType, YSMessage,
};
use rand::RngCore;

use crate::{
    creature::{property::append_int_prop_value, Creature, LifeState},
    entity::{Entity, VisionContext},
    gadget::WeaponGadget,
    gameserver_service, handler,
    network::PlayerSessionProxy,
    scene::{EnterSceneState, PlayerSceneComp, Scene},
};

use super::{
    avatar::PlayerAvatarComp, basic::PlayerBasicComp, item::PlayerItemComp, PlayerCoroExec,
    PlayerLoginComp,
};

pub struct Player {
    pub uid: u32,
    session: RefCell<sync::Weak<PlayerSessionProxy>>,
    pub player_basic_comp: Rc<RefCell<PlayerBasicComp>>,
    pub player_login_comp: Rc<RefCell<PlayerLoginComp>>,
    pub player_avatar_comp: Rc<RefCell<PlayerAvatarComp>>,
    pub player_scene_comp: Rc<RefCell<PlayerSceneComp>>,
    pub player_item_comp: Rc<RefCell<PlayerItemComp>>,
    pub peer_id: Cell<u32>,
    last_recv_ping_time: Cell<u64>,
    last_save_time: Cell<u64>,
    is_coro_stopping: Cell<bool>,
}

impl Player {
    pub fn construct_player(uid: u32) -> Self {
        Self {
            uid,
            session: RefCell::new(sync::Weak::new()),
            player_basic_comp: Rc::new(RefCell::new(PlayerBasicComp::default())),
            player_login_comp: Rc::new(RefCell::new(PlayerLoginComp::default())),
            player_avatar_comp: Rc::new(RefCell::new(PlayerAvatarComp::default())),
            player_scene_comp: Rc::new(RefCell::new(PlayerSceneComp::default())),
            player_item_comp: Rc::new(RefCell::new(PlayerItemComp::default())),
            peer_id: Cell::new(0),
            last_recv_ping_time: Cell::new(0),
            last_save_time: Cell::new(0),
            is_coro_stopping: Cell::new(false),
        }
    }

    pub fn login(
        &self,
        is_new_player: bool,
        is_relogin: bool,
        target_uid: u32,
        target_home_owner_uid: u32,
        is_client_relogin: bool,
    ) {
        tracing::info!("Player::login uid:{} is_new_player:{} is_relogin:{} target_uid:{} is_client_relogin:{}", self.uid, is_new_player, is_relogin, target_uid, is_client_relogin);

        self.update_last_ping_time();
        self.player_scene_comp.borrow_mut().pre_login(is_relogin);
        if is_new_player {
            self.first_login();
        }

        // TODO: checkLuaShellOnLogin

        self.player_basic_comp.borrow_mut().on_login(is_new_player);
        self.player_login_comp.borrow_mut().on_login(is_new_player);
        self.player_item_comp.borrow_mut().on_login(is_new_player);
        self.player_avatar_comp.borrow_mut().on_login(is_new_player);
        self.player_scene_comp.borrow_mut().on_login(is_new_player);

        // TODO: PlayerSceneComp::afterLogin

        if self.player_avatar_comp.borrow().get_avatar_count() == 0 {
            self.send_proto(DoSetPlayerBornDataNotify::default());
        } else {
            self.init_and_begin_enter_scene(
                false,
                target_uid,
                target_home_owner_uid,
                is_client_relogin,
            );
        }
    }

    fn first_login(&self) {
        self.player_basic_comp.borrow_mut().on_first_login();
        self.player_avatar_comp.borrow_mut().on_first_login();
        self.player_scene_comp.borrow_mut().on_first_login();
    }

    pub fn block_stop_coroutine(&self) -> Result<(), ()> {
        if self.is_coro_stopping.get() {
            tracing::error!("block_stop_coroutine called repeated, uid:{}", self.uid);
            return Err(());
        }

        self.is_coro_stopping.set(true);
        Ok(())
    }

    fn coroutine_loop(uid: u32, rx: Receiver<Packet>, session: Arc<PlayerSessionProxy>) {
        let player = Rc::new(Self::construct_player(uid));
        player.set_session(session, false);

        loop {
            if player.is_coro_stopping.get() {
                break;
            }

            let packet = rx.recv().unwrap();

            match packet.cmd_id {
                PlayerLoginRsp::CMD_ID => {
                    let rsp: PlayerLoginRsp = packet.get_proto().unwrap();
                    let player_data = PlayerData::decode(rsp.player_data.as_ref()).unwrap();
                    player.from_bin(player_data).unwrap();
                    player.init();

                    player.login(rsp.is_new_player, false, 0, 0, false);

                    let rsp = PlayerLoginRsp {
                        player_data: Vec::with_capacity(0),
                        ..rsp
                    };

                    player.send_proto(rsp);

                    // First post-login save
                    player.save_to_db();
                    player.last_save_time.set(time_utils::get_now());
                }
                _ => {
                    let _ = handler::process_packet(&player, packet);
                }
            }

            // TODO: configurable save time?
            if time_utils::get_now() - player.last_save_time.get() > 30 {
                player.save_to_db();
                player.last_save_time.set(time_utils::get_now());
            }
        }
    }

    pub fn start(session: Arc<PlayerSessionProxy>, uid: u32) -> PlayerCoroExec {
        PlayerCoroExec::start(move |rx| Self::coroutine_loop(uid, rx, session.clone()))
    }

    pub fn set_session(&self, session: Arc<PlayerSessionProxy>, _is_relogin: bool) {
        *self.session.borrow_mut() = Arc::downgrade(&session);
    }

    pub fn send_proto(&self, proto: impl YSMessage) {
        self.session
            .borrow()
            .upgrade()
            .unwrap()
            .send_packet(Packet {
                cmd_id: proto.get_cmd_id(),
                head: PacketHead::default(),
                recv_vec: proto.encode_to_vec(),
            });
    }

    pub fn on_ping_req(&self, req: PingReq, rsp: &mut PingRsp) {
        tracing::info!("[PING] uid:{}, seq:{}", self.uid, req.seq);
        rsp.retcode = Retcode::RetSucc.into();
        rsp.seq = req.seq;
        rsp.client_time = req.client_time;
        self.update_last_ping_time();
    }

    pub fn set_player_born_data(&self, req: SetPlayerBornDataReq) -> Result<(), Retcode> {
        if self.player_avatar_comp.borrow().get_avatar_count() != 0 {
            return Err(Retcode::RetRepeatSetPlayerBornData);
        }

        self.player_basic_comp
            .borrow()
            .check_nick_name(&req.nick_name)?;

        if self
            .player_avatar_comp
            .borrow_mut()
            .set_born_avatar(req.avatar_id)
            .is_err()
        {
            return Err(Retcode::RetAvatarIdError);
        }

        self.player_basic_comp.borrow_mut().nickname = req.nick_name;
        self.init_and_begin_enter_scene(false, 0, 0, false);

        Ok(())
    }

    pub fn check_is_in_safe_state_for_change_team(&self) -> Result<(), Retcode> {
        Ok(())
    }

    pub fn init_and_begin_enter_scene(
        &self,
        _is_relogin: bool,
        _target_uid: u32,
        _target_home_owner_uid: u32,
        _is_client_relogin: bool,
    ) {
        // Unlock all stuff here
        // TODO: MUIP/GM for this

        self.player_avatar_comp.borrow_mut().is_flyable = true;
        self.player_avatar_comp.borrow_mut().is_transferable = true;
        self.player_avatar_comp.borrow_mut().is_spring_auto_use = true;
        self.player_avatar_comp.borrow_mut().spring_auto_use_percent = 50;

        for open_state in data::iter_open_state_data() {
            self.player_basic_comp
                .borrow_mut()
                .open_state_map
                .insert(open_state.id, 1);
        }

        for avatar in data::iter_avatar_data() {
            if avatar.use_type == AvatarUseType::AvatarFormal {
                if !self.player_avatar_comp.borrow().has_avatar(avatar.id, 1) {
                    self.player_avatar_comp
                        .borrow_mut()
                        .add_formal_avatar(avatar.id, false, 1, 0)
                        .unwrap();
                }
            }
        }

        self.notify_all_data();

        let cur_scene_id = get_default_world_main_scene_id();

        let cur_scene = {
            let mut scene_comp = self.player_scene_comp.borrow_mut();
            scene_comp.dest_world_ptr = Some(scene_comp.own_world.clone());

            let dest_world = scene_comp.own_world.borrow();
            let cur_scene = dest_world.scene_map.get(&cur_scene_id).unwrap().clone();
            cur_scene.borrow_mut().base_mut().cur_world_wtr = Rc::downgrade(&scene_comp.own_world);

            cur_scene
        };

        let (pos, rot) = {
            let cur_scene = cur_scene.borrow_mut();
            cur_scene.base().get_last_owner_location()
        };

        self.begin_enter_scene(
            cur_scene,
            pos,
            rot,
            EnterType::EnterSelf,
            VisionType::VisionMeet,
        );
    }

    fn begin_enter_scene(
        &self,
        dest_scene_ptr: Rc<RefCell<Scene>>,
        pos: Vector3,
        rot: Vector3,
        enter_type: EnterType,
        vision_type: VisionType,
    ) {
        let mut scene_comp = self.player_scene_comp.borrow_mut();
        if scene_comp.enter_scene_state != EnterSceneState::ENTER_SCENE_NONE
            && scene_comp.enter_scene_state != EnterSceneState::ENTER_SCENE_POST
        {
            tracing::warn!(
                "begin_enter_scene with last one not finish, uid:{}",
                self.uid
            );
        }

        self.mark_destination(
            &dest_scene_ptr,
            pos.clone(),
            rot,
            enter_type,
            vision_type,
            &mut scene_comp,
        );

        let enter_scene_token = match scene_comp.enter_scene_token {
            0 => rand::thread_rng().next_u32() & 0xFFFF, // OG: gettid lmfao
            _ => scene_comp.enter_scene_token.wrapping_add(100),
        };

        scene_comp.enter_scene_token = enter_scene_token;

        let scene = dest_scene_ptr.borrow();
        let scene_id = scene.base().scene_id;
        let notify = PlayerEnterSceneNotify {
            scene_id,
            pos: Some(pos.to_client()),
            scene_transaction: scene.base().scene_transaction.clone(),
            enter_scene_token,
            scene_begin_time: scene.base().begin_time_ms,
            r#type: enter_type.into(),
            target_uid: self.uid,
            ..Default::default()
        };

        scene_comp.enter_scene_state = EnterSceneState::ENTER_SCENE_NOTIFY;

        tracing::info!("[EnterScene] BeginEnterScene uid:{} enter_type:{:?} token:{} dest_scene_id:{} pos:{:?} target_uid:{}", self.uid, enter_type, notify.enter_scene_token, scene_id, pos, 0);

        self.send_proto(notify);
    }

    pub fn enter_scene_ready(self: &Rc<Self>, req: &EnterSceneReadyReq) -> Result<(), Retcode> {
        {
            let scene_comp = self.player_scene_comp.borrow();

            if scene_comp.enter_scene_token != req.enter_scene_token {
                tracing::info!(
                    "[EnterScene] token not match, client_token:{} server_token:{}",
                    req.enter_scene_token,
                    scene_comp.enter_scene_token
                );

                return Err(Retcode::RetEnterSceneTokenInvalid);
            }

            if scene_comp.enter_scene_state != EnterSceneState::ENTER_SCENE_NOTIFY {
                tracing::warn!("EnterSceneState is not EnterSceneNotify, uid:{}", self.uid);
                return Err(Retcode::RetFail);
            }
        };

        self.pre_enter_scene()?;

        let mut scene_comp = self.player_scene_comp.borrow_mut();
        let dest_scene = scene_comp.get_dest_scene().unwrap();
        let dest_scene = dest_scene.borrow();
        let scene = dest_scene.base();
        let notify = EnterScenePeerNotify {
            enter_scene_token: scene_comp.enter_scene_token,
            dest_scene_id: scene_comp.dest_scene_id,
            host_peer_id: scene.get_host_peer_id().unwrap_or_default(),
            peer_id: scene.get_peer_id(self.uid).unwrap_or_default(),
        };

        tracing::info!(
            "[EnterScene] EnterSceneReady uid:{}, dest_scene_id:{}, peer_id:{}, host_peer_id:{}",
            self.uid,
            notify.dest_scene_id,
            notify.peer_id,
            notify.host_peer_id
        );

        scene_comp.enter_scene_state = EnterSceneState::ENTER_SCENE_READY;
        self.send_proto(notify);

        Ok(())
    }

    pub fn scene_init_finish(self: &Rc<Self>, req: &SceneInitFinishReq) -> Result<(), Retcode> {
        let scene_comp = self.player_scene_comp.borrow();

        if scene_comp.enter_scene_token != req.enter_scene_token {
            tracing::warn!(
                "[ENTER_SCENE] token not match, client_token:{} server_token:{}",
                req.enter_scene_token,
                scene_comp.enter_scene_token
            );

            return Err(Retcode::RetEnterSceneTokenInvalid);
        }

        if scene_comp.enter_scene_state != EnterSceneState::ENTER_SCENE_READY {
            tracing::warn!("EnterSceneState is not EnterSceneReady, uid:{}", self.uid);
            return Err(Retcode::RetFail);
        }

        let dest_pos = scene_comp.dest_pos.clone();
        let dest_rot = scene_comp.dest_rot.clone();
        let enter_type = scene_comp.dest_enter_type;
        let vision_type = scene_comp.dest_vision_type;
        let is_need_enter_world = {
            let Some(dest_scene) = scene_comp.get_dest_scene() else {
                tracing::error!("get_dest_scene failed, uid:{}", self.uid);
                return Err(Retcode::RetFail);
            };

            let is_reenter_dungeon = match scene_comp.get_cur_scene() {
                Some(_) => false,
                None => matches!(*dest_scene.borrow(), Scene::Dungeon(_)),
            };
            !is_reenter_dungeon
        };

        drop(scene_comp);

        if is_need_enter_world {
            let (world_owner_uid, dest_world_ptr) = {
                let scene_comp = self.player_scene_comp.borrow();
                let owner_uid = scene_comp
                    .dest_world_ptr
                    .as_ref()
                    .unwrap()
                    .borrow()
                    .get_owner_uid();
                let dest_world_ptr = scene_comp.dest_world_ptr.clone().unwrap();

                (owner_uid, dest_world_ptr)
            };

            let mut dest_world = dest_world_ptr.borrow_mut();

            if let Err(ret) = dest_world.player_enter(self) {
                tracing::info!(
                    "uid: {} player_enter world fails, owner_uid: {}",
                    self.uid,
                    world_owner_uid
                );
                return Err(ret);
            }
        }

        let dest_scene = self.player_scene_comp.borrow().get_dest_scene();
        self.player_enter_scene(dest_scene, dest_pos, dest_rot, enter_type, vision_type)?;
        self.player_scene_comp.borrow_mut().enter_scene_state = EnterSceneState::ENTER_SCENE_INIT;

        Ok(())
    }

    pub fn enter_scene_done(self: &Rc<Self>, req: &EnterSceneDoneReq) -> Result<(), Retcode> {
        let mut scene_comp = self.player_scene_comp.borrow_mut();

        if scene_comp.enter_scene_token != req.enter_scene_token {
            tracing::warn!(
                "[ENTER_SCENE] token not match, client_token:{} server_token:{}",
                req.enter_scene_token,
                scene_comp.enter_scene_token
            );
            return Err(Retcode::RetEnterSceneTokenInvalid);
        }

        if scene_comp.enter_scene_state != EnterSceneState::ENTER_SCENE_INIT {
            tracing::warn!("EnterSceneState is not EnterSceneInit, uid:{}", self.uid);
            return Err(Retcode::RetFail);
        }

        let Some(cur_scene) = scene_comp.get_cur_scene() else {
            tracing::warn!("get_cur_scene is None, uid:{}", self.uid);
            return Err(Retcode::RetFail);
        };

        let vision_type = scene_comp.dest_vision_type;
        let dest_pos = scene_comp.dest_pos.clone();
        let dest_rot = scene_comp.dest_rot.clone();

        let Some(cur_avatar_ptr) = ({
            let avatar_comp = self.player_avatar_comp.borrow();
            avatar_comp.get_cur_avatar()
        }) else {
            tracing::warn!("[EnterScene] get_cur_avatar failed, uid:{}", self.uid);
            return Err(Retcode::RetFail);
        };

        {
            let mut cur_avatar = cur_avatar_ptr.borrow_mut();
            if cur_avatar.get_life_state() == LifeState::LifeDead {
                cur_avatar.set_life_state(LifeState::LifeAlive);

                if cur_avatar.get_cur_hp() == 0.0 {
                    cur_avatar.set_cur_hp(1.0);
                }
            }

            cur_avatar.set_position(dest_pos.clone());
            cur_avatar.set_rotation(dest_rot.clone());
            cur_avatar.set_last_valid_position(dest_pos);
            cur_avatar.set_last_valid_rotation(dest_rot);
        }

        let vision_context = VisionContext::new(vision_type);
        if cur_scene
            .borrow_mut()
            .entity_appear(&(cur_avatar_ptr as Rc<RefCell<dyn Entity>>), vision_context)
            .is_err()
        {
            tracing::warn!("[EnterScene] entity_appear failed, uid:{}", self.uid);
            return Err(Retcode::RetFail);
        }

        scene_comp.enter_scene_state = EnterSceneState::ENTER_SCENE_DONE;
        // TODO: scene_comp.notify_current_weather();

        // Why here but ok..
        self.send_proto(EnterSceneDoneRsp {
            retcode: Retcode::RetSucc.into(),
            enter_scene_token: req.enter_scene_token,
        });

        tracing::info!("[EnterScene] enter_scene_done succ, uid:{}", self.uid);

        Ok(())
    }

    pub fn post_enter_scene(self: &Rc<Self>, req: &PostEnterSceneReq) -> Result<(), Retcode> {
        {
            let scene_comp = self.player_scene_comp.borrow();

            if scene_comp.enter_scene_token != req.enter_scene_token {
                tracing::warn!(
                    "[ENTER_SCENE] token not match, client_token:{} server_token:{}",
                    req.enter_scene_token,
                    scene_comp.enter_scene_token
                );
                return Err(Retcode::RetEnterSceneTokenInvalid);
            }

            if scene_comp.enter_scene_state != EnterSceneState::ENTER_SCENE_DONE {
                tracing::warn!("EnterSceneState is not EnterSceneDone, uid:{}", self.uid);
                return Err(Retcode::RetFail);
            }

            if scene_comp.get_cur_scene().is_none() {
                tracing::warn!("get_cur_scene is None, uid:{}", self.uid);
                return Err(Retcode::RetFail);
            };
        }

        self.clear_destination();
        self.player_scene_comp.borrow_mut().enter_scene_state = EnterSceneState::ENTER_SCENE_POST;
        // TODO: self.player_basic_comp.borrow_mut().is_in_transfer = false;

        tracing::info!("[EnterScene] post_enter_scene succ, uid:{}", self.uid);

        self.send_proto(PostEnterSceneRsp {
            retcode: Retcode::RetSucc.into(),
            enter_scene_token: req.enter_scene_token,
        });

        Ok(())
    }

    fn player_enter_scene(
        self: &Rc<Self>,
        dest_scene_ptr: Option<Rc<RefCell<Scene>>>,
        pos: Vector3,
        rot: Vector3,
        _enter_type: EnterType,
        _vision_type: VisionType,
    ) -> Result<(), Retcode> {
        let Some(dest_scene_ptr) = dest_scene_ptr else {
            tracing::error!("dest_scene_ptr is None, uid: {}", self.uid);
            return Err(Retcode::RetFail);
        };

        let cur_avatar_team = self.init_enter_scene_avatar(&mut dest_scene_ptr.borrow_mut())?;

        let (cur_avatar_team, appear_avatar) = {
            let avatar_comp = self.player_avatar_comp.borrow();
            let avatar_team = avatar_comp
                .avatar_team_map
                .get(&avatar_comp.cur_avatar_team_id)
                .unwrap();

            let cur_avatar_team = cur_avatar_team
                .iter()
                .map(|guid| avatar_comp.avatar_map.get(guid).unwrap().clone())
                .collect();

            let appear_avatar = avatar_comp
                .avatar_map
                .get(&avatar_team.borrow().last_cur_avatar_guid)
                .unwrap()
                .clone();

            (cur_avatar_team, appear_avatar)
        };

        if dest_scene_ptr
            .borrow_mut()
            .player_enter(self, pos, rot, false, cur_avatar_team, &appear_avatar)
            .is_err()
        {
            tracing::error!("player_enter fails, uid: {}", self.uid);
            return Err(Retcode::RetEnterSceneFail);
        }

        self.player_scene_comp.borrow_mut().cur_scene_wtr = Rc::downgrade(&dest_scene_ptr);

        Ok(())
    }

    pub fn notify_player_enter_scene_info(&self, scene: &mut Scene) {
        use crate::entity::Entity;
        // TODO: OG server gets scene from PlayerSceneComp here

        let cur_avatar_entity_id = {
            let Some(cur_avatar) = self.player_avatar_comp.borrow().get_cur_avatar() else {
                tracing::error!("cur_avatar_ptr is None");
                return;
            };

            let cur_avatar = cur_avatar.borrow();
            cur_avatar.entity_id()
        };

        let mut notify = PlayerEnterSceneInfoNotify::default();
        notify.cur_avatar_entity_id = cur_avatar_entity_id;
        notify.enter_scene_token = self.player_scene_comp.borrow().enter_scene_token;

        {
            let avatar_comp = self.player_avatar_comp.borrow();
            for avatar in avatar_comp.get_cur_avatar_team_vec() {
                let avatar = avatar.borrow();
                let weapon_gadget = avatar
                    .base_avatar()
                    .equip_comp
                    .weapon_gadget
                    .as_ref()
                    .unwrap();

                let weapon_gadget = weapon_gadget.borrow();
                notify.avatar_enter_info.push(AvatarEnterSceneInfo {
                    avatar_guid: avatar.base_avatar().guid,
                    weapon_guid: match &*weapon_gadget {
                        WeaponGadget::Avatar(gadget) => gadget.weapon_guid,
                        //_ => 0,
                    },
                    avatar_entity_id: avatar.entity_id(),
                    weapon_entity_id: weapon_gadget.entity_id(),
                    weapon_ability_info: Some(AbilitySyncStateInfo::default()),
                    avatar_ability_info: Some(AbilitySyncStateInfo::default()),
                    buff_id_list: Vec::with_capacity(0),
                    server_buff_list: Vec::with_capacity(0),
                });
            }

            let Some(avatar_team_entity) = avatar_comp.team_entity.upgrade() else {
                tracing::error!("team_entity is None");
                return;
            };

            let mut team_enter_info = TeamEnterSceneInfo::default();
            avatar_team_entity.borrow().to_client(&mut team_enter_info);
            notify.team_enter_info = Some(team_enter_info);
        }

        let mp_level_entity = scene.get_or_create_mp_level_entity();
        let mut mp_level_entity_info = MpLevelEntityInfo::default();
        mp_level_entity
            .borrow()
            .to_client(&mut mp_level_entity_info);

        notify.mp_level_entity_info = Some(mp_level_entity_info);
        self.send_proto(notify);
    }

    fn init_enter_scene_avatar(&self, dest_scene_ptr: &mut Scene) -> Result<Vec<u64>, Retcode> {
        // TODO: special avatars for dungeons/challenges

        let avatar_comp = self.player_avatar_comp.borrow();
        let cur_avatar_team = avatar_comp.get_cur_avatar_team_with_temp_avatar();

        let cur_world_ptr = dest_scene_ptr.base_mut().cur_world_wtr.upgrade().unwrap();
        let mut cur_world = cur_world_ptr.borrow_mut();

        let _team_avatar_vec = cur_world
            .scene_team
            .team_avatar_map
            .get_mut(&self.uid)
            .unwrap();

        /* TODO: check if this actually should be done here, I doubt tbh
        * cur_avatar_team.iter().for_each(|guid| {
            team_avatar_vec.push(crate::scene::SceneTeamAvatar {
                uid: self.uid,
                avatar_guid: *guid,
            })
        });*/

        // TODO: avatar_comp.is_all_avatar_dead() -> revive

        Ok(cur_avatar_team)
    }

    fn pre_enter_scene(self: &Rc<Self>) -> Result<(), Retcode> {
        let scene_comp = self.player_scene_comp.borrow();

        let Some(mut dest_world_ptr) = scene_comp.dest_world_ptr.as_ref().map(|d| d.borrow_mut())
        else {
            tracing::error!("dest_world_ptr is None, uid: {}", self.uid);
            return Err(Retcode::RetFail);
        };

        dest_world_ptr.player_pre_enter(self).map_err(|retcode| {
            tracing::warn!(
                "uid: {} pre-enter world fails, owner: {}",
                self.uid,
                dest_world_ptr.get_owner_uid()
            );
            retcode
        })?;

        scene_comp
            .get_dest_scene()
            .unwrap()
            .borrow_mut()
            .base_mut()
            .player_pre_enter(self.clone())
            .map_err(|retcode| {
                tracing::warn!("[EnterScene] playerPreEnter failed, uid:{}", self.uid);
                retcode
            })?;

        Ok(())
    }

    fn mark_destination(
        &self,
        dest_scene_ptr: &Rc<RefCell<Scene>>,
        pos: Vector3,
        rot: Vector3,
        enter_type: EnterType,
        vision_type: VisionType,
        comp: &mut PlayerSceneComp,
    ) {
        comp.dest_pos = pos;
        comp.dest_rot = rot;
        comp.set_dest_scene(Rc::downgrade(dest_scene_ptr));
        comp.dest_enter_type = enter_type;
        comp.dest_vision_type = vision_type;
    }

    fn clear_destination(&self) {
        let mut comp = self.player_scene_comp.borrow_mut();
        comp.set_dest_scene(Weak::new());
        comp.dest_enter_type = EnterType::EnterNone;
        comp.dest_vision_type = VisionType::VisionNone;
    }

    fn notify_all_data(&self) {
        self.notify_player_data();

        self.player_basic_comp.borrow().notify_all_data();
        self.player_item_comp.borrow().notify_all_data();
        self.player_avatar_comp.borrow().notify_all_data();
    }

    fn notify_player_data(&self) {
        let mut notify = PlayerDataNotify::default();

        let basic_comp = self.player_basic_comp.borrow();
        let avatar_comp = self.player_avatar_comp.borrow();
        let item_comp = self.player_item_comp.borrow();

        notify.nick_name = basic_comp.nickname.clone();
        notify.server_time = time_utils::get_now();

        let map = &mut notify.prop_map;
        append_int_prop_value(
            map,
            PROP_IS_SPRING_AUTO_USE,
            avatar_comp.is_spring_auto_use.into(),
        );
        append_int_prop_value(
            map,
            PROP_SPRING_AUTO_USE_PERCENT,
            avatar_comp.spring_auto_use_percent.into(),
        );
        append_int_prop_value(map, PROP_IS_FLYABLE, avatar_comp.is_flyable.into());
        append_int_prop_value(
            map,
            PROP_IS_TRANSFERABLE,
            avatar_comp.is_transferable.into(),
        );
        append_int_prop_value(
            map,
            PROP_IS_WEATHER_LOCKED,
            basic_comp.is_weather_locked.into(),
        );
        append_int_prop_value(
            map,
            PROP_IS_GAME_TIME_LOCKED,
            basic_comp.is_game_time_locked.into(),
        );
        append_int_prop_value(map, PROP_PLAYER_LEVEL, basic_comp.level.into());
        append_int_prop_value(map, PROP_PLAYER_EXP, basic_comp.exp.into());
        append_int_prop_value(map, PROP_PLAYER_HCOIN, item_comp.hcoin.into());
        append_int_prop_value(map, PROP_PLAYER_SCOIN, item_comp.scoin.into());
        append_int_prop_value(map, PROP_MAX_STAMINA, 10000);
        append_int_prop_value(map, PROP_CUR_PERSIST_STAMINA, 10000);

        self.send_proto(notify);
    }

    fn update_last_ping_time(&self) {
        self.last_recv_ping_time.set(time_utils::get_now());
    }

    pub fn save_to_db(&self) {
        use common::{net::ServiceType, ServiceQualifier};
        let player_data = self.to_bin();

        if let Err(err) = gameserver_service()
            .service_box
            .push_packet_to_service_blocking(
                ServiceQualifier::Type(ServiceType::Dbgate),
                Packet::new(
                    SavePlayerDataReq {
                        player_data: player_data.encode_to_vec(),
                        save_stat_id: 0, // TODO
                    },
                    PacketHead {
                        user_id: self.uid,
                        user_session_id: self
                            .session
                            .borrow()
                            .upgrade()
                            .map(|s| s.get_gateserver_session_id())
                            .unwrap_or_default(),
                        ..Default::default()
                    },
                ),
            )
        {
            tracing::warn!("save_to_db failed, uid: {}, err: {err}", self.uid);
        }
    }

    pub fn from_bin(&self, data: PlayerData) -> Result<(), prost::DecodeError> {
        // TODO: PlayerData::bin zlib
        let bin = PlayerDataBin::decode(&*data.bin)?;

        *self.player_basic_comp.borrow_mut() =
            PlayerBasicComp::from_bin(bin.basic_bin.unwrap_or_default());
        *self.player_login_comp.borrow_mut() =
            PlayerLoginComp::from_bin(bin.login_bin.unwrap_or_default());
        *self.player_avatar_comp.borrow_mut() =
            PlayerAvatarComp::from_bin(bin.avatar_bin.unwrap_or_default());
        *self.player_item_comp.borrow_mut() =
            PlayerItemComp::from_bin(bin.item_bin.unwrap_or_default());
        *self.player_scene_comp.borrow_mut() =
            PlayerSceneComp::from_bin(bin.scene_bin.unwrap_or_default());

        Ok(())
    }

    pub fn to_bin(&self) -> PlayerData {
        let mut bin = PlayerDataBin::default();
        bin.basic_bin = Some(self.player_basic_comp.borrow().to_bin());
        bin.login_bin = Some(self.player_login_comp.borrow().to_bin());
        bin.avatar_bin = Some(self.player_avatar_comp.borrow().to_bin());
        bin.item_bin = Some(self.player_item_comp.borrow().to_bin());
        bin.scene_bin = Some(self.player_scene_comp.borrow().to_bin());

        let basic_comp = self.player_basic_comp.borrow();
        PlayerData {
            uid: self.uid,
            nickname: basic_comp.nickname.clone(),
            level: basic_comp.level,
            exp: basic_comp.exp,
            bin: bin.encode_to_vec(),
            ..Default::default()
        }
    }

    pub fn init(self: &Rc<Self>) {
        self.player_basic_comp.borrow_mut().set_player(self.clone());
        self.player_login_comp.borrow_mut().set_player(self.clone());
        self.player_avatar_comp
            .borrow_mut()
            .set_player(self.clone());
        self.player_item_comp.borrow_mut().set_player(self.clone());
        self.player_scene_comp.borrow_mut().set_player(self.clone());

        self.player_basic_comp.borrow_mut().init();
        self.player_login_comp.borrow_mut().init();
        self.player_avatar_comp.borrow_mut().init();
        self.player_item_comp.borrow_mut().init();
        self.player_scene_comp.borrow_mut().init();
    }
}
