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

use data::{math_def::Vector3, prop_type::PROP_LEVEL, FightPropType};
use proto::{
    avatar_bin::{self, Detail},
    AbilitySyncStateInfo, AnimatorParameterValueInfoPair, AvatarBin, AvatarExcelInfo, Cjnoehnhdgl,
    EntityAuthorityInfo, EntityClientData, FightPropPair, Jgkjmgmfmpo, MotionInfo, PropPair,
    PropValue, ProtEntityType, SceneAvatarInfo, SceneEntityInfo,
};

use crate::{
    ability::AbilityComp,
    creature::{property::PropValueMap, Creature, FightPropComp, LifeState},
    entity::Entity,
    item::EquipComp,
    player::{item::Item, Player},
};

use super::{FormalAvatar, MirrorAvatar, TrialAvatar};

pub enum Avatar {
    Formal(FormalAvatar),
    Trial(TrialAvatar),
    Mirror(MirrorAvatar),
}

impl Avatar {
    pub fn base_avatar(&self) -> &BaseAvatar {
        match self {
            Self::Formal(a) => &a.base,
            Self::Trial(a) => &a.base,
            Self::Mirror(a) => &a.base,
        }
    }

    pub fn base_avatar_mut(&mut self) -> &mut BaseAvatar {
        match self {
            Self::Formal(a) => &mut a.base,
            Self::Trial(a) => &mut a.base,
            Self::Mirror(a) => &mut a.base,
        }
    }

    pub fn on_add_to_scene_team(&mut self) {
        self.base_avatar_mut().on_add_to_scene_team();
    }

    pub fn on_remove_from_scene_team(&mut self) {
        self.base_avatar_mut().on_remove_from_scene_team();
    }

    pub fn init(&mut self) {
        let base = self.base_avatar_mut();
        base.skill_depot_id = data::iter_avatar_data()
            .find(|a| a.id == base.avatar_id)
            .unwrap()
            .skill_depot_id;

        let player = self.base_avatar().player().unwrap();
        let item_comp = player.player_item_comp.borrow();

        let equip_item_vec = self
            .base_avatar()
            .equip_comp
            .equip_guid_vec
            .iter()
            .map(|guid| item_comp.pack_store.items.get(guid).unwrap())
            .collect::<Vec<_>>();

        let mut equip_vec = Vec::new();

        for item in equip_item_vec.iter() {
            match item {
                // TODO: Item::Equip -> Weapon, Relic
                Item::Weapon(weapon) => equip_vec.push(weapon),
            }
        }

        self.base_avatar_mut().equip_comp.init(equip_vec);
    }

    pub fn set_last_valid_position(&mut self, pos: Vector3) {
        self.base_avatar_mut().last_valid_pos = pos;
    }

    pub fn set_last_valid_rotation(&mut self, rot: Vector3) {
        self.base_avatar_mut().last_valid_rot = rot;
    }

    pub fn to_client(&self) -> proto::AvatarInfo {
        let mut info = match self {
            Self::Formal(avatar) => avatar.to_client(),
            Self::Trial(avatar) => avatar.to_client(),
            Self::Mirror(avatar) => avatar.to_client(),
        };

        info.avatar_type = self.get_avatar_type();
        info
    }

    pub fn to_client_scene_entity(&self, scene_entity_info: &mut SceneEntityInfo) {
        Entity::to_client(self, scene_entity_info);
    }

    pub fn to_client_avatar_entity(&self, scene_avatar_info: &mut SceneAvatarInfo) {
        let player = self.get_owner_player().unwrap();

        scene_avatar_info.uid = player.uid;
        scene_avatar_info.peer_id = player.peer_id.get();
        scene_avatar_info.avatar_id = self.base_avatar().avatar_id;
        scene_avatar_info.guid = self.base_avatar().guid;
        scene_avatar_info.equip_id_list = {
            let item_comp = player.player_item_comp.borrow();
            self.base_avatar()
                .equip_comp
                .equip_guid_vec
                .iter()
                .map(|guid| {
                    item_comp
                        .pack_store
                        .items
                        .get(guid)
                        .map(|item| item.item_id())
                })
                .flatten()
                .collect()
        };

        scene_avatar_info.skill_depot_id = self.base_avatar().skill_depot_id;
        scene_avatar_info.born_time = self.base_avatar().born_time;
        scene_avatar_info.team_resonance_list = vec![10301]; // TODO!
        scene_avatar_info.wearing_flycloak_id = self.base_avatar().flycloak_id;
        scene_avatar_info.costume_id = 0; // TODO!
        scene_avatar_info.excel_info = Some(AvatarExcelInfo::default());
        scene_avatar_info.weapon = self
            .base_avatar()
            .equip_comp
            .weapon_gadget
            .as_ref()
            .map(|gadget| gadget.borrow().to_client());
    }

    pub fn from_bin(bin: AvatarBin) -> Self {
        let level = bin.level;
        let cur_hp = bin.cur_hp;

        let mut avatar = match bin.detail.clone().unwrap() {
            Detail::FormalAvatar(detail) => Self::Formal(FormalAvatar::from_bin(bin, detail)),
            Detail::TrialAvatar(detail) => Self::Trial(TrialAvatar::from_bin(bin, detail)),
            Detail::MirrorAvatar(detail) => Self::Mirror(MirrorAvatar::from_bin(bin, detail)),
        };

        avatar.set_level(level);
        avatar.set_cur_hp(cur_hp);
        // TODO: cur_elem_energy

        avatar
    }

    pub fn to_bin(&self) -> AvatarBin {
        match self {
            Self::Formal(a) => a.to_bin(),
            Self::Trial(a) => a.to_bin(),
            Self::Mirror(a) => a.to_bin(),
        }
    }

    pub fn get_avatar_type(&self) -> u32 {
        match self {
            Self::Formal(_) => 1,
            Self::Trial(_) => 2,
            Self::Mirror(_) => 3,
        }
    }
}

#[derive(Default)]
pub struct BaseAvatar {
    player: Weak<Player>,
    pub avatar_id: u32,
    pub guid: u64,
    pub level: u32,
    pub life_state: LifeState,
    pub promote_level: u32,
    pub skill_depot_id: u32,
    // skill_map, buff_map, depot_map
    pub satiation_val: f32,
    pub satiation_penalty_time: f32,
    pub flycloak_id: u32,
    pub born_time: u32,
    // Components
    pub fight_prop_comp: FightPropComp,
    pub equip_comp: EquipComp,
    pub ability_comp: Rc<RefCell<AbilityComp>>,
    // Entity
    pub entity_id: u32,
    pub pos: Vector3,
    pub rot: Vector3,
    pub last_valid_pos: Vector3,
    pub last_valid_rot: Vector3,
}

impl BaseAvatar {
    pub fn to_client(&self, prop_map: PropValueMap) -> proto::AvatarInfo {
        let mut info = proto::AvatarInfo {
            avatar_id: self.avatar_id,
            guid: self.guid,
            prop_map,
            life_state: self.life_state as u32,
            skill_depot_id: self.skill_depot_id,
            wearing_flycloak_id: self.flycloak_id,
            born_time: self.born_time,
            fetter_info: Some(proto::AvatarFetterInfo {
                exp_level: 1,
                ..Default::default()
            }),
            fight_prop_map: self.fight_prop_comp.get_fight_prop_value_map(),
            ..Default::default()
        };
        self.equip_comp.to_client(&mut info);

        info
    }

    pub fn from_bin(bin: AvatarBin) -> Self {
        let mut fight_prop_comp = FightPropComp::default();
        fight_prop_comp.set_cur_hp(bin.cur_hp);

        let equip_comp = EquipComp::from_bin(&bin);

        Self {
            player: Weak::new(),
            avatar_id: bin.avatar_id,
            guid: bin.guid,
            level: bin.level,
            life_state: bin.life_state.into(),
            promote_level: bin.promote_level,
            skill_depot_id: bin.skill_depot_id,
            satiation_val: bin.satiation_val,
            satiation_penalty_time: bin.satiation_penalty_time,
            flycloak_id: bin.flycloak_id,
            born_time: bin.born_time,
            fight_prop_comp,
            equip_comp,
            ..Default::default()
        }
    }

    pub fn to_bin(&self, detail: avatar_bin::Detail) -> AvatarBin {
        let mut bin = AvatarBin {
            detail: Some(detail),
            avatar_id: self.avatar_id,
            guid: self.guid,
            level: self.level,
            life_state: self.life_state as u32,
            cur_hp: self.fight_prop_comp.get_prop_value(FightPropType::CurHp),
            cur_elem_energy: 0.0, // todo
            promote_level: self.promote_level,
            skill_depot_id: self.skill_depot_id,
            satiation_val: self.satiation_val,
            satiation_penalty_time: self.satiation_penalty_time,
            flycloak_id: self.flycloak_id,
            born_time: self.born_time,
            ..Default::default()
        };

        self.equip_comp.to_bin(&mut bin);
        bin
    }

    pub fn on_add_to_scene_team(&mut self) {
        // TODO
    }

    pub fn on_remove_from_scene_team(&mut self) {
        // TODO
    }

    pub fn set_player(&mut self, player: Rc<Player>) {
        self.player = Rc::downgrade(&player)
    }

    fn player(&self) -> Option<Rc<Player>> {
        self.player.upgrade()
    }
}

impl Creature for Avatar {
    fn set_level(&mut self, level: u32) {
        self.base_avatar_mut().level = level;
        self.assign_prop();
    }

    fn assign_prop(&mut self) {
        // TODO: level-based calculation
        let id = self.base_avatar().avatar_id;
        let config = data::iter_avatar_data().find(|a| a.id == id).unwrap();

        let base = self.base_avatar_mut();
        let fpc = &mut base.fight_prop_comp;

        fpc.fight_cal_prop_map
            .insert(FightPropType::BaseHp, config.hp_base);
        fpc.fight_cal_prop_map
            .insert(FightPropType::MaxHp, config.hp_base);
        fpc.fight_cal_prop_map
            .insert(FightPropType::BaseAttack, config.defense_base);
        fpc.fight_cal_prop_map
            .insert(FightPropType::BaseDefense, config.defense_base);
    }

    fn set_cur_hp_full(&mut self) {
        let base = self.base_avatar_mut();
        let fpc = &mut base.fight_prop_comp;

        fpc.set_cur_hp(fpc.get_prop_value(FightPropType::MaxHp));
    }

    fn get_life_state(&self) -> LifeState {
        self.base_avatar().life_state
    }

    fn set_life_state(&mut self, state: LifeState) {
        self.base_avatar_mut().life_state = state;
    }

    fn get_cur_hp(&self) -> f32 {
        let base = self.base_avatar();
        base.fight_prop_comp.get_prop_value(FightPropType::CurHp)
    }

    fn set_cur_hp(&mut self, hp: f32) {
        let base = self.base_avatar_mut();
        let fpc = &mut base.fight_prop_comp;

        fpc.set_cur_hp(hp);
    }
}

impl Entity for Avatar {
    fn get_entity_type(&self) -> ProtEntityType {
        ProtEntityType::ProtEntityAvatar
    }

    fn get_group_id(&self) -> u32 {
        0
    }

    fn entity_id(&self) -> u32 {
        self.base_avatar().entity_id
    }

    fn set_entity_id(&mut self, id: u32) {
        self.base_avatar_mut().entity_id = id
    }

    fn position(&self) -> &data::math_def::Vector3 {
        &self.base_avatar().pos
    }

    fn rotation(&self) -> &data::math_def::Vector3 {
        &self.base_avatar().rot
    }

    fn get_owner_player(&self) -> Option<Rc<Player>> {
        self.base_avatar().player()
    }

    fn is_on_scene(&self) -> bool {
        self.entity_id() != 0
    }

    fn set_position(&mut self, pos: data::math_def::Vector3) {
        self.base_avatar_mut().pos = pos;
    }

    fn set_rotation(&mut self, rot: data::math_def::Vector3) {
        self.base_avatar_mut().rot = rot;
    }

    fn to_client(&self, scene_entity_info: &mut SceneEntityInfo) {
        scene_entity_info.entity_id = self.entity_id();
        scene_entity_info.entity_type = self.get_entity_type().into();
        scene_entity_info.life_state = self.get_life_state() as u32;

        scene_entity_info.motion_info = Some(MotionInfo {
            pos: Some(self.position().to_client()),
            rot: Some(self.rotation().to_client()),
            speed: Some(proto::Vector::default()),
            ..Default::default()
        });

        // TODO: macro for this stuff
        scene_entity_info.prop_list = vec![PropPair {
            r#type: PROP_LEVEL,
            prop_value: Some(PropValue {
                r#type: PROP_LEVEL,
                val: self.base_avatar().level as i64,
                value: Some(proto::prop_value::Value::Ival(
                    self.base_avatar().level as i64,
                )),
            }),
        }];

        scene_entity_info.fight_prop_list = self
            .base_avatar()
            .fight_prop_comp
            .get_fight_prop_value_map()
            .iter()
            .map(|(ty, val)| FightPropPair {
                prop_type: *ty,
                prop_value: *val,
            })
            .collect();

        let mut scene_avatar_info = SceneAvatarInfo::default();
        self.to_client_avatar_entity(&mut scene_avatar_info);
        scene_entity_info.entity =
            Some(proto::scene_entity_info::Entity::Avatar(scene_avatar_info));

        scene_entity_info.animator_para_list = vec![AnimatorParameterValueInfoPair {
            animator_para: Some(Cjnoehnhdgl::default()),
            name_id: 0,
        }];

        scene_entity_info.entity_client_data = Some(EntityClientData::default());
        scene_entity_info.entity_authority_info = Some(EntityAuthorityInfo {
            ability_info: Some(AbilitySyncStateInfo::default()),
            born_pos: Some(proto::Vector::default()),
            client_extra_info: Some(Jgkjmgmfmpo {
                hekhfgcpgja: Some(proto::Vector::default()),
            }),
            ..Default::default()
        });
    }
}
