use data::tables::{self, AvatarBaseID};
use proto::{ItemStatic, PlayerDataBin, Retcode};

use super::gacha::GachaModel;
use super::game::{FrontendGame, FrontendGameError, GameInstance, LogicError};
use super::item::{ItemModel, ItemUID};
use super::main_city_model::MainCityModel;
use super::role::RoleModel;
use super::{BasicDataModel, LockModel};

#[derive(Default)]
pub struct Player {
    current_session_id: Option<u64>,
    pub game_instance: GameInstance,
    pub basic_data_model: BasicDataModel,
    pub lock_model: LockModel,
    pub role_model: RoleModel,
    pub item_model: ItemModel,
    pub main_city_model: MainCityModel,
    pub gacha_model: GachaModel,
}

impl Player {
    const MAIN_AVATAR_IDS: [u32; 2] = [2011, 2021];

    pub fn save_to_bin(&self) -> PlayerDataBin {
        PlayerDataBin {
            basic_data_model: Some(self.basic_data_model.to_bin()),
            lock_model: Some(self.lock_model.to_bin()),
            role_model: Some(self.role_model.to_bin()),
            item_model: Some(self.item_model.to_bin()),
            main_city_model: Some(self.main_city_model.to_bin()),
            gacha_model: Some(self.gacha_model.to_bin()),
        }
    }

    pub fn load_from_bin(bin: PlayerDataBin) -> Self {
        Self {
            basic_data_model: bin
                .basic_data_model
                .map(BasicDataModel::from_bin)
                .unwrap_or_default(),
            lock_model: bin.lock_model.map(LockModel::from_bin).unwrap_or_default(),
            role_model: bin.role_model.map(RoleModel::from_bin).unwrap_or_default(),
            item_model: bin.item_model.map(ItemModel::from_bin).unwrap_or_default(),
            main_city_model: bin
                .main_city_model
                .map(MainCityModel::from_bin)
                .unwrap_or_default(),
            gacha_model: bin.gacha_model.map(GachaModel::from_bin).unwrap_or_default(),
            ..Default::default()
        }
    }

    pub fn on_login(&mut self) {
        tables::unlock_config_template_tb::iter()
            .for_each(|tmpl| self.lock_model.add_unlock(tmpl.id))
    }

    pub fn on_first_login(&mut self) {
        self.item_model
            .add_resource(ItemStatic::FrontendGold as u32, 1_000_000);
        self.item_model
            .add_resource(ItemStatic::GameDiamond as u32, 1_000_000);
        self.item_model
            .add_resource(ItemStatic::GachaTicketEvent as u32, 30_000);
        self.item_model
            .add_resource(ItemStatic::GachaTicketStandard as u32, 30_000);
        self.item_model
            .add_resource(ItemStatic::GachaTicketBangboo as u32, 30_000);
        self.item_model.add_resource(ItemStatic::Energy as u32, 240);
    }

    pub fn init_frontend_game(&mut self) -> Result<(), LogicError> {
        let Some(frontend_avatar_id) = self.basic_data_model.frontend_avatar_id else {
            return Err(LogicError::from(FrontendGameError::NoFrontendAvatar));
        };

        let main_city = &self.main_city_model;
        self.game_instance = GameInstance::Frontend(
            FrontendGame::new(
                main_city.section_id,
                frontend_avatar_id,
                main_city.main_city_time.clone(),
                main_city.position.clone(),
                main_city.rotation.clone(),
            )
            .map_err(LogicError::from)?,
        );

        Ok(())
    }

    pub fn set_frontend_avatar(&mut self, id: AvatarBaseID) -> Result<(), Retcode> {
        (self.basic_data_model.frontend_avatar_id.is_none())
            .then_some(())
            .ok_or(Retcode::RetFail)?;

        (Self::MAIN_AVATAR_IDS.contains(&id.value()))
            .then_some(())
            .ok_or(Retcode::RetFail)?;

        self.basic_data_model.frontend_avatar_id = Some(id);
        Ok(())
    }

    pub fn dress_weapon(
        &mut self,
        avatar_id: AvatarBaseID,
        weapon_uid: ItemUID,
    ) -> Result<(), Retcode> {
        self.item_model
            .weapons
            .iter()
            .any(|w| w.uid == weapon_uid)
            .then_some(())
            .ok_or(Retcode::RetFail)?;

        self.role_model
            .avatar_list
            .iter_mut()
            .filter(|a| a.weapon_uid.map(|u| u.value()).unwrap_or_default() == weapon_uid.value())
            .for_each(|a| a.weapon_uid = None);

        let avatar = self
            .role_model
            .avatar_list
            .iter_mut()
            .find(|a| a.template_id == avatar_id)
            .ok_or(Retcode::RetFail)?;

        avatar.weapon_uid = Some(weapon_uid);
        Ok(())
    }

    pub fn set_current_session(&mut self, id: u64) {
        self.current_session_id = Some(id);
    }

    pub fn current_session_id(&self) -> Option<u64> {
        self.current_session_id
    }
}
