use std::collections::HashSet;

use crate::logic::components::{ParaglidingSkin, RoleSkin, SoarWingSkin, WeaponSkin};
use crate::logic::ecs::component::ComponentContainer;
use crate::logic::player::ItemUsage;
use crate::logic::role::{Role, RoleFormation};
use crate::logic::thread_mgr::NetContext;
use crate::modify_component;
use wicked_waifus_protocol::{
    ArrayIntInt, ClientCurrentRoleReportRequest, ClientCurrentRoleReportResponse,
    ERemoveEntityType, EntityAddNotify, EntityEquipSkinChangeNotify, EntityFlySkinChangeData,
    EntityPb, EntityRemoveInfo, EntityRemoveNotify, EquipFlySkinData, ErrorCode, FlySkinConfigData,
    FlySkinWearAllRoleRequest, FlySkinWearAllRoleResponse, FlySkinWearRequest, FlySkinWearResponse,
    FormationAttrRequest, FormationAttrResponse, PbUpLevelRoleRequest, PbUpLevelRoleResponse,
    PlayerMotionRequest, PlayerMotionResponse, RoleBreakThroughViewRequest,
    RoleBreakThroughViewResponse, RoleFavorListRequest, RoleFavorListResponse,
    RoleFlyEquipChangeNotify, RoleLevelUpViewRequest, RoleLevelUpViewResponse,
    RoleShowListUpdateRequest, RoleShowListUpdateResponse, RoleSkinChangeRequest,
    RoleSkinChangeResponse, SoarWingOrParaglidingSkinChangeNotify, UnlockRoleSkinListRequest,
    UnlockRoleSkinListResponse, UpdateFormationRequest, UpdateFormationResponse,
    WeaponSkinComponentPb,
};

pub fn on_role_show_list_update_request(
    ctx: &mut NetContext,
    request: RoleShowListUpdateRequest,
    response: &mut RoleShowListUpdateResponse,
) {
    let role_ids: HashSet<i32> = ctx.player.role_list.keys().cloned().collect();
    let all_exist = request.role_list.iter().all(|id| role_ids.contains(id));

    if all_exist {
        ctx.player.basic_info.role_show_list = request.role_list;
        response.error_code = ErrorCode::Success.into();
    } else {
        response.error_code = ErrorCode::InvalidRequest.into(); // TODO: replace with appropriate error code
    }
}

pub fn on_client_current_role_report_request(
    _ctx: &NetContext,
    request: ClientCurrentRoleReportRequest,
    response: &mut ClientCurrentRoleReportResponse,
) {
    response.current_entity_id = request.current_entity_id;
    response.player_id = request.player_id;
}

pub fn on_role_favor_list_request(
    _ctx: &NetContext,
    _request: RoleFavorListRequest,
    response: &mut RoleFavorListResponse,
) {
    response.favor_list = vec![]; // TODO: add favor
    response.error_code = ErrorCode::Success.into();
}

pub fn on_formation_attr_request(
    _ctx: &NetContext,
    _request: FormationAttrRequest,
    response: &mut FormationAttrResponse,
) {
    response.error_code = ErrorCode::Success.into();
}

pub fn on_update_formation_request(
    ctx: &mut NetContext,
    request: UpdateFormationRequest,
    response: &mut UpdateFormationResponse,
) {
    let world = ctx.world.get_mut_world_entity();

    for formation in request.formations {
        let formation_id = formation.formation_id;
        let cur_role = formation.cur_role;
        let is_current = formation.is_current;

        if is_current {
            // update player current formation id
            ctx.player.cur_formation_id = formation_id;

            // search old formation id and set real_formation_id, set is_current to false
            let mut real_formation_id = formation_id;
            if let Some(rf) = ctx
                .player
                .formation_list
                .values_mut()
                .find(|rf| rf.is_current && rf.id != formation_id)
            {
                real_formation_id = rf.id;
                rf.is_current = false;
            }

            if let Some(old_formation) = ctx.player.formation_list.get(&real_formation_id) {
                let removed_entities: Vec<i64> = old_formation
                    .role_ids
                    .iter()
                    .map(|&role_id| world.get_entity_id(role_id))
                    .collect();
                removed_entities.iter().for_each(|&entity_id| {
                    world.remove_entity(entity_id as i32);
                });
                ctx.player
                    .notify(ctx.player.build_player_entity_remove_notify(
                        removed_entities,
                        ERemoveEntityType::RemoveTypeNormal,
                    ));
            }

            let added_roles: Vec<&Role> = formation
                .role_ids
                .iter()
                .map(|role_id| ctx.player.role_list.get(role_id).unwrap())
                .collect();

            if !added_roles.is_empty() {
                // add new roles
                ctx.player.notify(
                    ctx.player
                        .build_player_entity_add_notify(added_roles, world),
                );
            }

            // send update group formation notify
            ctx.player
                .notify(ctx.player.build_update_group_formation_notify(
                    RoleFormation {
                        id: formation_id,
                        cur_role,
                        role_ids: formation.role_ids.clone(),
                        is_current,
                    },
                    world,
                ));

            response.formation = Some(formation.clone());
        }

        // update all formation and check formation_list
        ctx.player
            .formation_list
            .entry(formation_id)
            .and_modify(|r| {
                r.cur_role = formation.cur_role;
                r.role_ids = formation.role_ids.clone();
                r.is_current = is_current;
            })
            .or_insert(RoleFormation {
                id: formation_id,
                cur_role: formation.cur_role,
                role_ids: formation.role_ids,
                is_current,
            });
    }

    ctx.player
        .notify(ctx.player.build_update_formation_notify());

    response.error_code = ErrorCode::Success.into();
}

pub fn on_player_motion_request(
    _: &NetContext,
    request: PlayerMotionRequest,
    response: &mut PlayerMotionResponse,
) {
    match wicked_waifus_data::motion_data::iter().find(|&motion| motion.id == request.motion) {
        None => response.error_id = ErrorCode::UnKnownError.into(),
        Some(_) => response.error_id = ErrorCode::Success.into(),
    }
}

pub fn on_unlock_role_skin_list_request(
    ctx: &NetContext,
    _request: UnlockRoleSkinListRequest,
    response: &mut UnlockRoleSkinListResponse,
) {
    response.role_skin_list = ctx.player.unlocked_skins.role_skins.iter().cloned().collect();
}

pub fn on_role_skin_change_request(
    ctx: &mut NetContext,
    request: RoleSkinChangeRequest,
    response: &mut RoleSkinChangeResponse,
) {
    // TODO: Should we verify role id first against bindata?
    let role = ctx.player.role_list.get_mut(&request.role_id);
    let Some(role) = role else {
        response.error_code = ErrorCode::NotValidRole.into();
        return;
    };

    // Verify Id exist in bindata
    let Some(skin_data) =
        wicked_waifus_data::role_skin_data::iter().find(|data| data.id == request.skin_id)
    else {
        response.error_code = ErrorCode::ErrRoleSkinConfig.into();
        return;
    };

    // Verify Skin is unlocked
    if !ctx.player.unlocked_skins.role_skins.contains(&skin_data.id) {
        response.error_code = ErrorCode::ErrRoleSkinLocked.into();
        return;
    }

    // Check that role has this skin
    if skin_data.role_id != role.role_id {
        response.error_code = ErrorCode::ErrRoleSkinNotMatch.into();
        return;
    }

    role.skin_id = request.skin_id;
    if request.is_wear_weapon_skin {
        if skin_data.suit_weapon_skin_id == 0 {
            response.error_code = ErrorCode::ErrRoleSkinWeaponNotSuit.into();
            return;
        }
        role.weapon_skin_id = skin_data.suit_weapon_skin_id;
    }

    let world = ctx.world.get_world_entity();
    let entity_id = world.get_entity_id(request.role_id);
    modify_component!(
        world.get_entity_components(entity_id as i32),
        RoleSkin,
        |skin_component: &mut RoleSkin| {
            skin_component.skin_id = role.skin_id;
        }
    );
    if request.is_wear_weapon_skin {
        // Check for suit_weapon_skin_id == 0 has already been done
        modify_component!(
            world.get_entity_components(entity_id as i32),
            WeaponSkin,
            |skin_component: &mut WeaponSkin| {
                skin_component.skin_id = skin_data.suit_weapon_skin_id;
            }
        );
        // Since the whole entity is recreated this shouldn't be needed but meh, whatever
        ctx.player.notify(EntityEquipSkinChangeNotify {
            entity_id,
            weapon_skin_component_pb: Some(WeaponSkinComponentPb {
                weapon_skin_id: skin_data.suit_weapon_skin_id,
            }),
        });
    }
    ctx.player.notify(EntityRemoveNotify {
        remove_infos: vec![EntityRemoveInfo {
            entity_id,
            r#type: 0,
        }],
        is_remove: false,
    });

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

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

    ctx.player.notify(EntityAddNotify {
        entity_pbs: vec![pb],
        remove_tag_ids: false,
    });
    // player.notify(player.build_update_group_formation_notify(aaa, world));
    ctx.player
        .notify(ctx.player.build_update_formation_notify());
    response.error_code = ErrorCode::Success.into();
}

pub fn on_fly_skin_wear_request(
    ctx: &mut NetContext,
    request: FlySkinWearRequest,
    response: &mut FlySkinWearResponse,
) {
    let role = ctx.player.role_list.get_mut(&request.role_id);
    let Some(role) = role else {
        response.error_code = ErrorCode::NotValidRole.into();
        return;
    };

    // Verify Id exist in bindata
    let skin =
        wicked_waifus_data::fly_skin_config_data::iter().find(|&skin| skin.id == request.skin_id);
    let Some(skin) = skin else {
        response.error_code = ErrorCode::NoFlySkinItem.into();
        return;
    };

    match skin.skin_type {
        0 => {
            // Verify Skin is unlocked
            if !ctx.player.unlocked_skins.fly_skins.contains(&skin.id) {
                response.error_code = ErrorCode::ErrRoleSkinLocked.into();
                return;
            }
            role.fly_skin_id = request.skin_id
        }
        1 => {
            if !ctx.player.unlocked_skins.wing_skins.contains(&skin.id) {
                response.error_code = ErrorCode::ErrRoleSkinLocked.into();
                return;
            }
            role.wing_skin_id = request.skin_id
        }
        _ => {
            response.error_code = ErrorCode::FlySkinTypeErr.into();
            return;
        }
    }
    let world = ctx.world.get_world_entity();
    let entity_id = world.get_entity_id(request.role_id);
    match skin.skin_type {
        0 => {
            modify_component!(
                world.get_entity_components(entity_id as i32),
                SoarWingSkin,
                |skin_component: &mut SoarWingSkin| {
                    skin_component.skin_id = role.skin_id;
                }
            );
        }
        1 => {
            modify_component!(
                world.get_entity_components(entity_id as i32),
                ParaglidingSkin,
                |skin_component: &mut ParaglidingSkin| {
                    skin_component.skin_id = role.skin_id;
                }
            );
        }
        _ => unreachable!("Already tested above"),
    }
    ctx.player.notify(SoarWingOrParaglidingSkinChangeNotify {
        fly_skin_data: vec![EntityFlySkinChangeData {
            entity_id,
            fly_skin_config_data: vec![FlySkinConfigData {
                skin_id: request.skin_id,
                fly_skin_id: skin.skin_type,
            }],
        }],
    });

    ctx.player.notify(RoleFlyEquipChangeNotify {
        fly_skin_data: vec![EquipFlySkinData {
            role_id: request.role_id,
            skin_id: request.skin_id,
        }],
    });
    response.error_code = ErrorCode::Success.into();
}

pub fn on_fly_skin_wear_all_role_request(
    ctx: &mut NetContext,
    request: FlySkinWearAllRoleRequest,
    response: &mut FlySkinWearAllRoleResponse,
) {
    let skin =
        wicked_waifus_data::fly_skin_config_data::iter().find(|&skin| skin.id == request.skin_id);
    let Some(skin) = skin else {
        response.error_code = ErrorCode::NoFlySkinItem.into();
        return;
    };

    match skin.skin_type {
        0 => {
            // Verify Skin is unlocked
            if !ctx.player.unlocked_skins.fly_skins.contains(&skin.id) {
                response.error_code = ErrorCode::ErrRoleSkinLocked.into();
                return;
            }
            for role in ctx.player.role_list.values_mut() {
                role.fly_skin_id = request.skin_id;
            }
        }
        1 => {
            if !ctx.player.unlocked_skins.wing_skins.contains(&skin.id) {
                response.error_code = ErrorCode::ErrRoleSkinLocked.into();
                return;
            }
            for role in ctx.player.role_list.values_mut() {
                role.wing_skin_id = request.skin_id;
            }
        }
        _ => {
            response.error_code = ErrorCode::FlySkinTypeErr.into();
            return;
        }
    }
    ctx.player.notify(RoleFlyEquipChangeNotify {
        fly_skin_data: ctx
            .player
            .role_list
            .values()
            .map(|r| EquipFlySkinData {
                role_id: r.role_id,
                skin_id: request.skin_id,
            })
            .collect::<Vec<_>>(),
    });
    let world = ctx.world.get_world_entity();
    let data = ctx
        .player
        .role_list
        .values()
        .filter_map(|role| {
            let entity_id = world.get_entity_id(role.role_id);
            if entity_id == -1 {
                None
            } else {
                match skin.skin_type {
                    0 => {
                        modify_component!(
                            world.get_entity_components(entity_id as i32),
                            SoarWingSkin,
                            |skin_component: &mut SoarWingSkin| {
                                skin_component.skin_id = role.skin_id;
                            }
                        );
                    }
                    1 => {
                        modify_component!(
                            world.get_entity_components(entity_id as i32),
                            ParaglidingSkin,
                            |skin_component: &mut ParaglidingSkin| {
                                skin_component.skin_id = role.skin_id;
                            }
                        );
                    }
                    _ => unreachable!("Already tested above"),
                }
                Some(EntityFlySkinChangeData {
                    entity_id,
                    fly_skin_config_data: vec![FlySkinConfigData {
                        skin_id: request.skin_id,
                        fly_skin_id: skin.skin_type,
                    }],
                })
            }
        })
        .collect::<Vec<_>>();
    ctx.player.notify(SoarWingOrParaglidingSkinChangeNotify {
        fly_skin_data: data,
    });
    response.error_code = ErrorCode::Success.into();
}

pub fn on_role_level_up_view_request(
    ctx: &mut NetContext,
    request: RoleLevelUpViewRequest,
    response: &mut RoleLevelUpViewResponse,
) {
    let role = ctx.player.role_list.get(&request.role_id);
    let Some(role) = role else {
        response.error_code = ErrorCode::NotValidRole.into();
        return;
    };

    response.level = role.level;
    // TODO: shall we get from data? seems client can do it by himself
    response.level_exp_info = vec![ArrayIntInt { key: 1, value: 200 }];
    response.exp = role.exp;
    // it seems add_exp, final_prop, cost_list amd overflow_list are handled by client, so skip

    let items = wicked_waifus_data::role_exp_item_data::iter()
        .map(|(&id, _)| id)
        .collect::<Vec<_>>();
    response.item_list = ctx.player.inventory.to_array_int_int_filtered(&items);
    response.error_code = ErrorCode::Success.into();
}

pub fn on_pb_up_level_role_request(
    ctx: &mut NetContext,
    request: PbUpLevelRoleRequest,
    response: &mut PbUpLevelRoleResponse,
) {
    response.role_id = request.role_id;
    let role = ctx.player.role_list.get(&request.role_id);
    let Some(role) = role else {
        response.error_code = ErrorCode::NotValidRole.into();
        return;
    };

    // TODO: no shell_credit??? :turtle_skull:
    let items = ctx.player.inventory.consume_items(
        &request
            .item_list
            .iter()
            .map(|item| ItemUsage {
                id: item.key,
                quantity: -item.value,
            })
            .collect::<Vec<_>>(),
    );
    let Ok(_items) = items else {
        response.error_code = ErrorCode::ErrConsumeNotEnough.into();
        return;
    };

    let mut total_exp = request
        .item_list
        .iter()
        .filter_map(|item| {
            wicked_waifus_data::role_exp_item_data::get(&item.key)
                .map(|exp| exp.basic_exp * item.value)
        })
        .sum();

    let role_level_consume_id = wicked_waifus_data::role_info_data::iter()
        .find(|role| role.id == request.role_id)
        .map(|role| role.level_consume_id)
        .unwrap_or(10001);

    let mut levels_consume = wicked_waifus_data::role_level_consume_data::iter()
        .filter(|consume| {
            consume.consume_group_id == role_level_consume_id && consume.level > role.level
        }) // TODO: add upper bound too(till breakthrough)
        .collect::<Vec<_>>();
    levels_consume.sort_by_key(|item| item.level);

    let mut level = role.level;
    for level_consume in levels_consume {
        if level_consume.exp_count > total_exp {
            break;
        }
        total_exp -= level_consume.exp_count;
        level = level_consume.level;
    }
    response.level = level;
    response.exp = total_exp;

    // TODO is item_map the overflowing items or all items used? also should we sent more notifies?
    response.error_code = ErrorCode::Success.into();
}

// on_role_break_through_view_request

pub fn on_role_break_through_view_request(
    ctx: &mut NetContext,
    request: RoleBreakThroughViewRequest,
    response: &mut RoleBreakThroughViewResponse,
) {
    let role = ctx.player.role_list.get(&request.role_id);
    let Some(role) = role else {
        response.error_code = ErrorCode::NotValidRole.into();
        return;
    };

    // TODO:
    // if !condition {
    //     response.error_code = ErrorCode::ErrRoleConditionNotFind.into();
    //     response.is_condition_finish = false;
    // }

    let role_breach_id = wicked_waifus_data::role_info_data::iter()
        .find(|role| role.id == request.role_id)
        .map(|role| role.breach_id)
        .unwrap_or(request.role_id);

    let condition = wicked_waifus_data::role_breach_data::iter()
        .find(|role_breach_data| {
            role_breach_data.breach_group_id == role_breach_id
                && role_breach_data.breach_level == role.breakthrough
        })
        .unwrap(); // TODO: handling

    response.error_code = ErrorCode::Success.into();
    response.level_limit = condition.max_level;

    response.cost_list = condition
        .breach_consume
        .iter()
        .map(|(&id, &count)| ArrayIntInt {
            key: id,
            value: count,
        })
        .collect::<Vec<_>>();
    // TODO: un_lock_skill_id, reward_list, final_prop
    response.is_condition_finish = true; // is this last??
}
