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

use common::tools::time_utils;
use proto::{Retcode, WorldBin};
use tokio::sync::OnceCell;

use crate::player::Player;

use super::{scene_mgr, Scene, SceneTeam};

pub struct WorldPlayerSlotInfo {
    pub uid: u32,
    pub pre_enter_time: u64,
}

pub struct WorldPlayerInfo {
    pub player_wtr: Weak<Player>,
    pub enter_time: u64,
    pub cur_scene_id: u32,
}

#[derive(Default)]
pub struct PlayerWorld {
    player: OnceCell<Weak<Player>>,
    pub scene_team: SceneTeam,
    slot_info_map: HashMap<u32, WorldPlayerSlotInfo>,
    player_info_map: HashMap<u32, WorldPlayerInfo>,
    pub scene_map: BTreeMap<u32, Rc<RefCell<Scene>>>,
    pub level: u32,
    pub last_adjust_time: u32,
    pub adjust_level: u32,
}

impl PlayerWorld {
    pub fn set_player(&self, player: Rc<Player>) {
        self.player.set(Rc::downgrade(&player)).unwrap();
        self.scene_map
            .values()
            .for_each(|scene| scene.borrow_mut().set_own_player(player.clone()));
    }

    pub fn player(&self) -> Option<Rc<Player>> {
        self.player.get().map(|p| p.upgrade())?
    }

    pub fn init(&mut self) {
        if self.scene_map.len() != 0 {
            for (_, scene) in self.scene_map.iter_mut() {
                scene.borrow_mut().init();
            }

            let default_scene_id = data::get_default_world_main_scene_id();

            // TODO: last scene id
            self.scene_team.init(
                self.player().unwrap(),
                self.player().unwrap().uid,
                default_scene_id,
            );
        } else {
            let default_scene_id = data::get_default_world_main_scene_id();
            let mut scene = scene_mgr::create_scene(default_scene_id, self.player().unwrap());
            scene.init();
            self.scene_map
                .insert(default_scene_id, Rc::new(RefCell::new(scene)));
            tracing::info!(
                "[SCENE] first create scene, scene_id:{}, uid: {}",
                default_scene_id,
                self.player().unwrap().uid
            );

            self.scene_team.init(
                self.player().unwrap(),
                self.player().unwrap().uid,
                default_scene_id,
            );
        }
    }

    pub fn get_main_world_scene(&self) -> Option<Rc<RefCell<Scene>>> {
        self.scene_map
            .get(&data::get_default_world_main_scene_id())
            .cloned()
    }

    pub fn pre_player_login(&mut self, is_relogin: bool) {
        for (_, scene) in self.scene_map.iter_mut() {
            scene.borrow_mut().pre_player_login(is_relogin);
        }
    }

    pub fn player_pre_enter(&mut self, player: &Player) -> Result<(), Retcode> {
        if self.is_world_full() {
            tracing::warn!("uid: {} player_pre_enter fails, world is full", player.uid);
            return Err(Retcode::RetFail);
        }

        let owner_uid = self.get_owner_uid();

        if self
            .slot_info_map
            .insert(
                player.uid,
                WorldPlayerSlotInfo {
                    uid: player.uid,
                    pre_enter_time: time_utils::get_now(),
                },
            )
            .is_some()
        {
            tracing::warn!(
                "uid: {} already pre_enter world, owner: {}",
                player.uid,
                owner_uid
            );
        }

        // TODO: World::notifyPreEnterMpPlayer

        tracing::info!(
            "[WORLD] uid: {} pre-Enter world, owner: {}",
            player.uid,
            owner_uid
        );

        Ok(())
    }

    pub fn player_enter(&mut self, player: &Rc<Player>) -> Result<(), Retcode> {
        let Some(own_player) = self.player() else {
            tracing::error!("get_own_player fails");
            return Err(Retcode::RetFail);
        };

        if let Err(ret) = self.check_player_enter(player) {
            tracing::warn!(
                "[WORLD] uid: {} enter world fail, owner: {}, ret: {:?}",
                player.uid,
                own_player.uid,
                ret
            );
            return Err(ret);
        }

        tracing::info!(
            "[WORLD] uid: {} enter world, owner: {}",
            player.uid,
            own_player.uid
        );

        self.player_info_map.insert(
            player.uid,
            WorldPlayerInfo {
                player_wtr: Rc::downgrade(player),
                enter_time: time_utils::get_now(),
                cur_scene_id: 0,
            },
        );

        self.scene_team.on_player_enter(player.clone());

        // TODO: World::notifyAllPlayerInfo
        // TODO: PlayerWorld::notifyWorldAndOwnerData

        if self.get_player_count() > 1 {
            // MP stuff
            // TODO: PlayerWorld::onPlayerCountChange
            // TODO: World::notifyEnterWorldChatChannel
        }

        Ok(())
    }

    pub fn check_player_enter(&self, player: &Player) -> Result<(), Retcode> {
        let own_player = self.player().ok_or(Retcode::RetFail)?;

        if self.is_world_full() {
            tracing::error!(
                "uid: {} enter owner: {} world fails",
                player.uid,
                own_player.uid
            );
            return Err(Retcode::RetFail);
        }

        if own_player.uid != player.uid {
            if !self.player_info_map.contains_key(&own_player.uid) {
                tracing::warn!(
                    "owner_uid:{} not in world, uid:{}",
                    own_player.uid,
                    player.uid
                );
                return Err(Retcode::RetMpGuestLoadingFirstEnter);
            }
        }

        if !self.slot_info_map.contains_key(&player.uid) {
            tracing::warn!(
                "world owner: {} cannot found slot for uid: {}",
                own_player.uid,
                player.uid
            );
            return Err(Retcode::RetFail);
        }

        Ok(())
    }

    fn is_world_full(&self) -> bool {
        self.get_player_count() >= self.get_max_player_count()
    }

    pub fn get_player_count(&self) -> usize {
        self.scene_team.get_team_player_num()
    }

    pub fn get_max_player_count(&self) -> usize {
        4
    }

    pub fn get_owner_uid(&self) -> u32 {
        self.player().map(|p| p.uid).unwrap_or_default()
    }

    pub fn from_bin(bin: WorldBin) -> Self {
        Self {
            scene_map: bin
                .scene_map
                .into_iter()
                .map(|(id, sc)| (id, Rc::new(RefCell::new(Scene::from_bin(id, sc)))))
                .collect(),
            scene_team: SceneTeam::default(),
            level: bin.level,
            last_adjust_time: bin.last_adjust_time,
            adjust_level: bin.adjust_level,
            player: OnceCell::new(),
            slot_info_map: HashMap::new(),
            player_info_map: HashMap::new(),
        }
    }

    pub fn to_bin(&self) -> WorldBin {
        WorldBin {
            scene_map: self
                .scene_map
                .iter()
                .map(|(id, sc)| (*id, sc.borrow().to_bin()))
                .collect(),
            level: self.level,
            last_adjust_time: self.last_adjust_time,
            adjust_level: self.adjust_level,
        }
    }
}
