use wicked_waifus_protocol::summon::ESummonType;
use wicked_waifus_protocol::{
    EEntityType, ERemoveEntityType, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo, EntityRemoveNotify, EntityState, FightBuffInformation, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, SceneMode, ScenePlayerInformation, SceneTimeInfo
};

use wicked_waifus_data::pb_components::ComponentsData;
use wicked_waifus_data::{
    base_property_data, blueprint_config_data, summon_cfg_data, template_config_data, EntityLogic, EntityType, LevelEntityConfigData
};

use crate::logic::components::{Autonomous, Fsm, Interact, MonsterAi, SoarWingSkin, StateTag, Tag};
use crate::logic::ecs::entity::{Entity, EntityBuilder};
use crate::logic::ecs::world::{World, WorldEntity};
use crate::logic::math::Transform;
use crate::logic::player::Player;
use crate::logic::role::RoleFormation;
use crate::logic::utils::growth_utils::get_monster_props_by_level;
use crate::logic::utils::{entity_serializer, tag_utils};
use crate::logic::{
    components::{
        Attribute, Concomitant, EntityConfig, Equip, FightBuff, Movement, OwnerPlayer,
        PlayerOwnedEntityMarker, Position, RoleSkin, Summoner, Visibility, VisionSkill,
    },
    ecs::component::ComponentContainer,
};
//use crate::resonator_data::{ResonatorData, Concomitant, SummonerComponent};

use crate::query_with;

pub fn summon_concomitant(player: &Player, world: &mut WorldEntity, template_cfg: &wicked_waifus_data::TemplateConfigData, cur_summon_id: i32) -> (Entity, Vec<i64>) {
    let mut concomitant_buffs: Vec<FightBuffInformation> = Vec::new();

    let summon_cfg = summon_cfg_data::get(&template_cfg.blueprint_type).unwrap();
    let concomitant_config_id = template_cfg.id;

    let con_entity = world.create_entity(
        cur_summon_id,
        EEntityType::Monster.into(),
        player.basic_info.cur_map_id,
    );

    for buff_id in &summon_cfg.born_buff_id {
        concomitant_buffs.push(world.create_buff(cur_summon_id, *buff_id));
    }

    tracing::info!("Adding Concomitant with id: {} and buffs {:#?}", concomitant_config_id, concomitant_buffs);

    (world
        .create_builder(con_entity)
        .with(ComponentContainer::PlayerOwnedEntityMarker(PlayerOwnedEntityMarker {
            entity_type: EEntityType::Monster,
        }))
        .with(ComponentContainer::EntityConfig(EntityConfig {
            camp: 0,
            config_id: concomitant_config_id,
            config_type: EntityConfigType::Template,
            entity_type: EEntityType::Monster,
            entity_state: EntityState::Born,
        }))
        .with(ComponentContainer::OwnerPlayer(OwnerPlayer(
            player.basic_info.id,
        )))
        .with(ComponentContainer::Position(Position(
            player.location.position.clone(),
        )))
        .with(ComponentContainer::Visibility(Visibility {
            is_visible: false,
            is_actor_visible: true,
        }))
        .with(ComponentContainer::Attribute(Attribute::from_data(
            base_property_data::iter()
                .find(|d| d.id == template_cfg.components_data.attribute_component.clone().unwrap().property_id.unwrap())
                .unwrap(),
            None,
            None,
        )))
        .with(ComponentContainer::FightBuff(FightBuff { fight_buff_infos: concomitant_buffs, ..Default::default() }))
        .with(ComponentContainer::Summoner(Summoner {
            summon_cfg_id: summon_cfg.id,
            summon_skill_id: 0,
            summon_type: ESummonType::ESummonTypeConcomitantCustom.into()
        }))
        .with(ComponentContainer::Autonomous(Autonomous { autonomous_id: 3 }))
        .build(), summon_cfg.born_buff_id.clone())
}

fn add_player_entity(player: &Player, formation: &RoleFormation, world: &mut WorldEntity) {
    let role_vec = formation
        .role_ids
        .iter()
        .map(|role_id| player.role_list.get(role_id).unwrap())
        .collect::<Vec<_>>();

    tracing::info!("adding player entity for formation {:#?}", formation.role_ids);
    let cur_role_id = formation.cur_role;

    let mut pbs = Vec::new();

    for role in role_vec {
        let entity = world.create_entity(
            role.role_id,
            EEntityType::Player.into(),
            player.basic_info.cur_map_id,
        );

        let mut fight_buff_infos = world.generate_role_permanent_buffs(entity.entity_id, role.role_id);

        // TODO: add actual weapon switching and remove this! - rabby
        let equip_weapon = match role.role_id {
            1409 => 21020056, // cartethyia
            1207 => 21010036, // lupa
            1301 => 21010036,
            _ => role.equip_weapon,
        };

        let mut concomitants: Vec<i64> = vec![];
        let mut concom_pbs = Vec::new();

        let role_data = wicked_waifus_data::role_info_data::iter().find(|r| r.id == role.role_id).unwrap();
        if let Some(skin_damage_first) = role_data.skin_damage.first() {
            let role_name = skin_damage_first.as_str()
                .split('/').next_back()
                .and_then(|s| s.strip_prefix("DA_"))
                .and_then(|s| s.split('_').next()).unwrap();

            let mut summon_id = 1000;
            for model_config in wicked_waifus_data::model_config_preload_data::iter().filter(|cfg| {
                cfg.actor_class_path.starts_with("/Game/Aki/Character/Monster/Summon/") && cfg.actor_class_path.contains(role_name)
            }) {
                let template_cfg = wicked_waifus_data::template_config_data::iter().find(|cfg| {
                    let template_model_component = cfg.1.components_data.model_component.clone();
                    template_model_component.is_some() 
                    && 
                    template_model_component.clone().unwrap().model_type.unwrap().model_id.is_some() 
                    && 
                    template_model_component.unwrap().model_type.unwrap().model_id.unwrap() == model_config.id
                }).unwrap().1;

                let (concomitant, buffs) = summon_concomitant(player, world, template_cfg, summon_id);
                let mut pb = EntityPb {
                    id: summon_id as i64,
                    ..Default::default()
                };

                for buff_id in buffs {
                    fight_buff_infos.push(world.create_buff(entity.entity_id, buff_id));
                }

                world
                    .get_entity_components(summon_id)
                    .into_iter()
                    .for_each(|comp| comp.set_pb_data(&mut pb));
                concom_pbs.push(pb);
                summon_id += 1;
                concomitants.push(concomitant.entity_id.into());
            }

            player.notify(EntityAddNotify {
                entity_pbs: concom_pbs,
                remove_tag_ids: true,
            });
        }
        fight_buff_infos.dedup_by(|x, z| x.buff_id == z.buff_id);

        let buf_manager = FightBuff {
            fight_buff_infos,
            list_buff_effect_cd: vec![]
        };

        let entity = world
            .create_builder(entity)
            .with(ComponentContainer::PlayerOwnedEntityMarker(
                PlayerOwnedEntityMarker {
                    entity_type: EEntityType::Player,
                },
            ))
            .with(ComponentContainer::EntityConfig(EntityConfig {
                camp: 0,
                config_id: role.role_id,
                config_type: EntityConfigType::Character,
                entity_type: EEntityType::Player,
                entity_state: EntityState::Default,
            }))
            .with(ComponentContainer::OwnerPlayer(OwnerPlayer(
                player.basic_info.id,
            )))
            .with(ComponentContainer::Position(Position(
                player.location.position.clone(),
            )))
            .with(ComponentContainer::Visibility(Visibility {
                is_visible: role.role_id == cur_role_id,
                is_actor_visible: true,
            }))
            // TODO: from role
            // TODO: Check if role has hardness or rage_mode
            // TODO: Support AddProp from Equipment(Echo, weapon, buffs??), weapon base state goes to base_prop too.
            .with(ComponentContainer::Attribute(Attribute::from_data(
                &role.get_base_properties(),
                None,
                None,
            )))
            .with(ComponentContainer::Movement(Movement::default()))
            .with(ComponentContainer::Equip(Equip {
                weapon_id: equip_weapon,
                weapon_breach_level: 0, // TODO: store this too
            }))
            .with(ComponentContainer::VisionSkill(VisionSkill {
                skill_id: player.explore_tools.active_explore_skill,
            }))
            .with(ComponentContainer::RoleSkin(RoleSkin {
                skin_id: role.skin_id,
            }))
            .with(ComponentContainer::SoarWingSkin(SoarWingSkin {
                skin_id: 84000001,
            }))
            .with(ComponentContainer::FightBuff(buf_manager))
            .with(ComponentContainer::Concomitant(Concomitant {
                vision_entity_id: 0,
                custom_entity_ids: concomitants,
                phantom_role_id: 0
            }))
            .build();

        let mut pb = EntityPb {
            id: entity.entity_id as i64,
            ..Default::default()
        };

        world
            .get_entity_components(entity.entity_id)
            .into_iter()
            .for_each(|comp| comp.set_pb_data(&mut pb));
        pbs.push(pb);
        
        tracing::debug!(
            "created player entity, id: {}, role_id: {}",
            entity.entity_id,
            role.role_id
        );
    }

    player.notify(EntityAddNotify {
        entity_pbs: pbs,
        remove_tag_ids: true,
    });
}

pub fn add_player_entities(player: &Player, formation: &RoleFormation, possible_world: Option<&mut WorldEntity>) {
    match possible_world {
        Some(world) => add_player_entity(player, formation, world),
        None => {
            let mut world_ref = player.world.borrow_mut();
            add_player_entity(player, formation, world_ref.get_mut_world_entity())
        }
    };
}

pub fn build_scene_information(player: &Player) -> SceneInformation {
    SceneInformation {
        scene_id: String::new(),
        instance_id: player.location.instance_id,
        owner_id: player.basic_info.id,
        dynamic_entity_list: Vec::new(),
        blackboard_params: Vec::new(),
        end_time: 0,
        aoi_data: Some(entity_serializer::build_scene_add_on_init_data(player)),
        player_infos: build_player_info_list(&player.world.borrow_mut()),
        mode: SceneMode::Single.into(),
        time_info: Some(SceneTimeInfo {
            owner_time_clock_time_span: 0,
            hour: 8,
            minute: 0,
        }),
        cur_context_id: player.basic_info.id as i64,
        ..Default::default()
    }
}

fn build_player_info_list(world: &World) -> Vec<ScenePlayerInformation> {
    world
        .players()
        .map(|sp| {
            let (cur_role_id, transform, _equip) = query_with!(
                world.get_world_entity(),
                PlayerOwnedEntityMarker,
                OwnerPlayer,
                Visibility,
                EntityConfig,
                Position,
                Equip
            )
            .into_iter()
            .find_map(|(_, _, owner, visibility, conf, pos, equip)| {
                (sp.player_id == owner.0 && visibility.is_visible).then_some((
                    conf.config_id,
                    pos.0.clone(),
                    equip.weapon_id,
                ))
            })
            .unwrap_or_default();

            let active_characters = query_with!(
                world.get_world_entity(),
                PlayerOwnedEntityMarker,
                OwnerPlayer,
                EntityConfig,
                RoleSkin
            )
            .into_iter()
            .filter(|(_, _, owner, _, _)| owner.0 == sp.player_id);

            ScenePlayerInformation {
                cur_role: cur_role_id,
                group_type: sp.group_type,
                player_id: sp.player_id,
                player_icon: sp.player_icon,
                player_name: sp.player_name.clone(),
                level: sp.level,
                location: Some(transform.get_position_protobuf()),
                rotation: Some(transform.get_rotation_protobuf()),
                fight_role_infos: Vec::from([FightRoleInfos {
                    group_type: sp.group_type,
                    living_status: LivingStatus::Alive.into(),
                    cur_role: cur_role_id,
                    // is_retain: true,
                    fight_role_infos: active_characters
                        .map(|(id, _, _, conf, _role_skin)| FightRoleInfo {
                            entity_id: id.into(),
                            role_id: conf.config_id,
                            on_stage_without_control: false,
                        })
                        .collect(),
                    ..Default::default()
                }]),
                // vehicle_player_data: Vec::ne(),
                ..Default::default()
            }
        })
        .collect()
}

pub fn remove_entity(player: &Player, entity_id: i64, remove_type: ERemoveEntityType) {
    let mut world_ref = player.world.borrow_mut();
    let world = world_ref.get_mut_world_entity();

    if world.remove_entity(entity_id as i32) {
        player.notify(EntityRemoveNotify {
            remove_infos: vec![EntityRemoveInfo {
                entity_id,
                r#type: remove_type.into(),
            }],
            is_remove: true,
        });
    }
}

pub fn remove_entities(player: &Player, entities: &[&LevelEntityConfigData]) {
    let mut removed_entities = Vec::with_capacity(entities.len());
    // Enclose to drop borrow mut ASAP
    {
        let mut world_ref = player.world.borrow_mut();
        let world = world_ref.get_mut_world_entity();

        for entity in entities {
            let entity_id = entity.entity_id as i32; // TODO: Should be i64
            if world.remove_entity(entity_id) {
                removed_entities.push(world.get_entity_id(entity_id));
            }
        }
    }
    for entity_id in removed_entities {
        player.notify(EntityRemoveNotify {
            remove_infos: vec![EntityRemoveInfo {
                entity_id,
                r#type: 0,
            }],
            is_remove: true,
        });
    }
}

pub fn add_entities(player: &Player, entities: &[&LevelEntityConfigData], external_awake: bool) {
    let mut added_entities = Vec::with_capacity(entities.len());
    // Enclose to drop borrow mut ASAP
    {
        let mut world_ref = player.world.borrow_mut();
        let world = world_ref.get_mut_world_entity();

        for entity in entities {
            // Skip hidden entities
            if entity.is_hidden {
                tracing::debug!("Hidden entity with config id: {}", entity.entity_id);
                continue;
            }
            if entity.in_sleep && !external_awake {
                tracing::debug!(
                    "Sleep entity with config id not spawned: {}",
                    entity.entity_id
                );
                continue;
            }

            let blueprint_config = blueprint_config_data::get(&entity.blueprint_type);
            let template_config = template_config_data::get(&entity.blueprint_type);
            if blueprint_config.is_none() || template_config.is_none() {
                continue;
            }

            let entity_logic: EntityLogic = blueprint_config.unwrap().entity_logic;
            let (config_type, entity_type, mut entity_state) = match entity_logic {
                EntityLogic::Item => (
                    EntityConfigType::Level,
                    EEntityType::SceneItem,
                    EntityState::Default,
                ),
                EntityLogic::Animal => (
                    EntityConfigType::Level,
                    EEntityType::Animal,
                    EntityState::Default,
                ),
                EntityLogic::Monster => (
                    EntityConfigType::Level,
                    EEntityType::Monster,
                    EntityState::Born,
                ),
                EntityLogic::Vehicle => (
                    EntityConfigType::Level,
                    EEntityType::Vehicle,
                    EntityState::Default,
                ),
                EntityLogic::Npc => (
                    EntityConfigType::Level,
                    EEntityType::Npc,
                    EntityState::Default,
                ),
                EntityLogic::Vision => (
                    EntityConfigType::Level,
                    EEntityType::Vision,
                    EntityState::Default,
                ),
                EntityLogic::ClientOnly => (
                    EntityConfigType::Level,
                    EEntityType::ClientOnly,
                    EntityState::Default,
                ),
                EntityLogic::ServerOnly => {
                    tracing::debug!("Unhandled entity to be added of logic: {:?} with blueprint_type {} and id: {}", entity_logic, entity.blueprint_type, entity.entity_id);
                    continue;
                }
                EntityLogic::Custom => (
                    EntityConfigType::Level,
                    EEntityType::Custom,
                    EntityState::Default,
                ),
            };

            if entity.in_sleep {
                entity_state = EntityState::Sleep;
            }

            let config_id = entity.entity_id as i32; // TODO: i64????
            let map_id = entity.map_id;
            let components: ComponentsData = entity
                .components_data
                .merge_with_template(&template_config.unwrap().components_data);
            let tmp_entity = world.create_entity(config_id, config_type.into(), map_id);
            let mut builder = world.create_builder(tmp_entity);
            builder
                .with(ComponentContainer::EntityConfig(EntityConfig {
                    camp: components
                        .base_info_component
                        .as_ref()
                        .and_then(|b| b.camp)
                        .unwrap_or(0),
                    config_id,
                    config_type,
                    entity_type,
                    entity_state,
                }))
                .with(ComponentContainer::Position(Position(Transform::from(
                    &entity.transform[..],
                ))))
                .with(ComponentContainer::Visibility(Visibility {
                    is_visible: true,
                    is_actor_visible: true,
                }))
                // Some entities may not actually have movement, but it's okay since we won't
                // receive move package push for them
                .with(ComponentContainer::Movement(Movement::default()));

            build_autonomous_component(&mut builder, player.basic_info.id, entity_logic);
            build_interact_component(&mut builder, &components);
            build_tags_components(
                &mut builder,
                &components,
                player,
                blueprint_config.unwrap().entity_type,
                config_id as i64,
            );
            build_attribute_component(&mut builder, &components, player.location.instance_id);
            build_ai_components(&mut builder, &components);
            added_entities.push(builder.build());
        }
    }

    let world_ref = player.world.borrow();
    let world = world_ref.get_world_entity();
    // Since kuro has issues, we can only send one
    for entity in added_entities {
        let mut pb = EntityPb {
            id: entity.entity_id as i64, // TODO: Should be i64
            ..Default::default()
        };

        world
            .get_entity_components(entity.entity_id)
            .into_iter()
            .for_each(|comp| comp.set_pb_data(&mut pb));

        player.notify(EntityAddNotify {
            entity_pbs: vec![pb],
            remove_tag_ids: true,
        });
    }
}

#[inline(always)]
fn build_autonomous_component(builder: &mut EntityBuilder, id: i32, logic: EntityLogic) {
    // TODO: Review if other types have autonomous
    match logic {
        EntityLogic::Item => {
            builder.with(ComponentContainer::Autonomous(Autonomous {
                autonomous_id: id,
            }));
        }
        EntityLogic::Animal => {}
        EntityLogic::Monster => {}
        EntityLogic::Vehicle => {}
        EntityLogic::Npc => {}
        EntityLogic::Vision => {}
        EntityLogic::ClientOnly => {}
        EntityLogic::ServerOnly => {}
        EntityLogic::Custom => {}
    };
}

#[inline(always)]
fn build_interact_component(builder: &mut EntityBuilder, components: &ComponentsData) {
    if components.interact_component.is_some() {
        builder.with(ComponentContainer::Interact(Interact {}));
    }
}

#[inline(always)]
fn build_tags_components(
    builder: &mut EntityBuilder,
    components: &ComponentsData,
    player: &Player,
    entity_type: EntityType,
    config_id: i64,
) {
    if let Some(entity_state_component) = &components.entity_state_component {
        let state = match entity_type {
            EntityType::Teleporter | EntityType::TemporaryTeleporter => {
                let result = player
                    .teleports
                    .teleports_data
                    .iter()
                    .find(|teleporter| teleporter.entity_config_id == config_id);
                match result.is_some() {
                    true => tag_utils::get_tag_id_by_name("关卡.Common.状态.激活"),
                    false => tag_utils::get_tag_id_by_name("关卡.Common.状态.常态"),
                }
            }
            _ => {
                // TODO: how to get states???
                let unlocked = false;
                match unlocked {
                    true => tag_utils::get_tag_id_by_name("关卡.Common.状态.激活"),
                    false => tag_utils::get_tag_id_by_name("关卡.Common.状态.常态"),
                }
            }
        };
        builder
            .with(ComponentContainer::StateTag(StateTag {
                state_tag_id: state,
            }))
            .with(ComponentContainer::Tag(Tag {
                // TODO:
                gameplay_tags: vec![],
                entity_common_tags: vec![state],
                // TODO:
                init_gameplay_tag: false,
            }));
    }
}

#[inline(always)]
fn build_attribute_component(
    builder: &mut EntityBuilder,
    components: &ComponentsData,
    instance_id: i32,
) {
    if let Some(attribute_component) = &components.attribute_component {
        if attribute_component.disabled.unwrap_or_default() {
            return;
        }
        if let Some(property_id) = attribute_component.property_id {
            let inst_data = wicked_waifus_data::instance_dungeon_data::iter()
                .find(|d| d.id == instance_id)
                .unwrap();

            let mut level = inst_data.entity_level;
            if level == 0 {
                // TODO:
                //  - iterate area_data
                //  - find player.basic_info.area_id
                //  - get player world level
                //  - get area.world_monster_level_max
                level = 90;
            }
            builder.with(ComponentContainer::Attribute(Attribute::from_data(
                &get_monster_props_by_level(property_id, level),
                attribute_component.hardness_mode_id,
                attribute_component.rage_mode_id,
            )));
        }
    }
}

#[inline(always)]
fn build_ai_components(builder: &mut EntityBuilder, components: &ComponentsData) {
    if let Some(ai_component) = &components.ai_component {
        builder.with(ComponentContainer::MonsterAi(MonsterAi {
            weapon_id: ai_component
                .weapon_id
                .as_deref()
                .and_then(|id| id.parse().ok())
                .unwrap_or(0),
            hatred_group_id: 0,   // TODO:
            ai_team_init_id: 100, // TODO:
            combat_message_id: 0, // TODO:
        }));
        if let Some(ai_id) = ai_component.ai_id {
            builder.with(ComponentContainer::Fsm(Fsm::from_ai_id(ai_id)));
        }
    }
}
