use std::{
    cmp::min,
    collections::{
        hash_map::Entry::{Occupied, Vacant},
        HashSet,
    },
};

use data::{
    gacha::{gacha_config::*, global_gacha_config},
    tables::{AvatarBaseID, WeaponID},
};

use super::*;
use chrono::{DateTime, Local};
use proto::*;

pub async fn on_get_gacha_data(
    _session: &NetSession,
    _player: &mut Player,
    req: GetGachaDataCsReq,
) -> NetResult<GetGachaDataScRsp> {
    if req.gacha_type != 3 {
        // tracing::info!("non-supported gacha type {}", body.gacha_type);
        Ok(GetGachaDataScRsp {
            retcode: Retcode::RetSucc.into(),
            gacha_type: req.gacha_type,
            gacha_data: Some(GachaData::default()),
        })
    } else {
        // tracing::info!("construct gacha info");
        Ok(GetGachaDataScRsp {
            retcode: Retcode::RetSucc.into(),
            gacha_type: req.gacha_type,
            gacha_data: Some(generate_all_gacha_info(_player, &Local::now())),
        })
    }
}

pub async fn on_do_gacha(
    _session: &NetSession,
    _player: &mut Player,
    req: DoGachaCsReq,
) -> NetResult<DoGachaScRsp> {
    let gachaconf = global_gacha_config();
    let gacha_model = &mut _player.gacha_model;
    let item_model = &mut _player.item_model;
    let role_model = &mut _player.role_model;
    let pull_time = Local::now();
    let target_pool = get_gacha_pool(
        &gachaconf.character_gacha_pool_list,
        &req.gacha_parent_schedule_id,
        &pull_time,
    );
    if let None = target_pool {
        tracing::info!(
            "refuse gacha because: pool of parent_schedule_id {} not found",
            req.gacha_parent_schedule_id
        );
        return Ok(DoGachaScRsp {
            retcode: Retcode::RetFail.into(),
            ..Default::default()
        });
    };
    let target_pool = target_pool.unwrap();

    // tracing::info!("cost_item_count: {}", req.cost_item_count);
    let mut pull_count = if req.cost_item_count > 1 { 10 } else { 1 };
    let cost_count = pull_count;

    if pull_count == 10 {
        let discount_tag = &gachaconf.common_properties.ten_pull_discount_tag;
        if target_pool.discount_policy_tags.contains(&discount_tag) {
            let gacha_bin = &mut gacha_model.gacha_bin;
            let status_bin = gacha_bin
                .gacha_status_map
                .get_mut(&target_pool.sharing_guarantee_info_category)
                .unwrap();
            let discount_policy = gachaconf
                .discount_policies
                .ten_pull_discount_map
                .get(discount_tag)
                .unwrap();
            let usage = status_bin.discount_usage_map.get_mut(discount_tag).unwrap();
            if *usage < discount_policy.use_limit {
                *usage += 1;
                // cost_count = discount_policy.discounted_prize;
            }
        }
    }
    if cost_count != req.cost_item_count {
        tracing::info!(
            "refuse gacha because: expected cost item {cost_count}, found {}",
            req.cost_item_count
        );
        return Ok(DoGachaScRsp {
            retcode: Retcode::RetFail.into(),
            ..Default::default()
        });
    } else {
        // TODO: cost resource
    }

    let mut gain_item_list: Vec<GainItemInfo> = vec![];
    while pull_count > 0 {
        let pull_result = gacha_model.perform_pull_pool(&pull_time, target_pool);
        let extra_item_bin = pull_result.extra_item_bin.clone().unwrap();
        let uid = match GachaAddedItemType::try_from(pull_result.item_type) {
            Ok(enum_val) => match enum_val {
                GachaAddedItemType::Weapon => match WeaponID::new(pull_result.obtained_item_id) {
                    Some(id) => item_model.add_weapon(id).value(),
                    None => 0,
                },
                GachaAddedItemType::Character => {
                    match AvatarBaseID::new(pull_result.obtained_item_id) {
                        Some(id) => {
                            role_model.add_avatar(id);
                            0
                        }
                        None => 0,
                    }
                }
                _ => 0,
            },
            Err(_err) => 0,
        };
        if extra_item_bin.extra_item_id != 0 {
            item_model.add_resource(
                extra_item_bin.extra_item_id,
                extra_item_bin.extra_item_count,
            );
        }
        gain_item_list.push(GainItemInfo {
            item_id: pull_result.obtained_item_id,
            extra_item_id: extra_item_bin.extra_item_id,
            extra_item_count: extra_item_bin.extra_item_count,
            uid,
            num: 1,
            ..GainItemInfo::default()
        });
        pull_count -= 1;
        gacha_model.gacha_bin.gacha_records.push(pull_result);
    }

    Ok(DoGachaScRsp {
        retcode: Retcode::RetSucc.into(),
        gain_item_list,
        gacha_data: Some(generate_all_gacha_info(_player, &pull_time)),
        cost_item_count: req.cost_item_count,
    })
}

pub async fn on_gacha_free_agent(
    _session: &NetSession,
    _player: &mut Player,
    req: GachaFreeAgentCsReq,
) -> NetResult<GachaFreeAgentScRsp> {
    let gachaconf = global_gacha_config();
    let role_model = &mut _player.role_model;
    let pull_time = Local::now();
    let target_pool = get_gacha_pool(
        &gachaconf.character_gacha_pool_list,
        &req.gacha_parent_schedule_id,
        &pull_time,
    );
    if let None = target_pool {
        tracing::info!(
            "refuse free agent because: pool of parent_schedule_id {} not found",
            req.gacha_parent_schedule_id
        );
        return Ok(GachaFreeAgentScRsp {
            retcode: Retcode::RetFail.into(),
            ..Default::default()
        });
    };
    let target_pool = target_pool.unwrap();
    let gacha_bin = &mut _player.gacha_model.gacha_bin;
    let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category;
    let status_bin = gacha_bin
        .gacha_status_map
        .get_mut(sharing_guarantee_category_tag)
        .unwrap();

    let mut free_select_policy: Option<&FreeSelectItem> = None;
    let mut free_select_progress: u32 = 0;
    let mut free_select_required_pull: u32 = 0;
    for discount_policy_tag in target_pool.discount_policy_tags.iter() {
        if gachaconf
            .discount_policies
            .free_select_map
            .contains_key(discount_policy_tag)
        {
            let policy = gachaconf
                .discount_policies
                .free_select_map
                .get(discount_policy_tag)
                .unwrap();
            let free_select_demand_idx = usize::try_from(
                *(status_bin
                    .discount_usage_map
                    .get(&policy.free_select_usage_record_tag)
                    .unwrap()),
            )
            .unwrap();
            if policy.milestones.len() <= free_select_demand_idx {
                continue;
            }

            let free_select_actual_progress = status_bin
                .discount_usage_map
                .get(&policy.free_select_progress_record_tag)
                .unwrap();
            free_select_policy = Some(policy);
            free_select_required_pull = policy
                .milestones
                .get(free_select_demand_idx)
                .unwrap()
                .to_owned();
            free_select_progress = min(free_select_required_pull, *free_select_actual_progress);
        }
    }

    if let None = free_select_policy {
        tracing::info!(
            "refuse free agent because: pool of parent_schedule_id {} hasn't defined free agent discount yet (or used up chance)",
            req.gacha_parent_schedule_id
        );
        return Ok(GachaFreeAgentScRsp {
            retcode: Retcode::RetFail.into(),
            ..Default::default()
        });
    } else if free_select_progress < free_select_required_pull {
        tracing::info!(
            "refuse free agent because: use pulled {free_select_progress} (after last free agent) in parent_schedule_id {}, required {free_select_required_pull}",
            req.gacha_parent_schedule_id
        );
        return Ok(GachaFreeAgentScRsp {
            retcode: Retcode::RetFail.into(),
            ..Default::default()
        });
    }
    let free_select_policy = free_select_policy.unwrap();
    let mut has_demanded_item = false;
    for rarity_items in target_pool.gacha_items.iter() {
        if rarity_items.rarity != free_select_policy.rarity {
            continue;
        }
        for (category_tag, category) in rarity_items.categories.iter() {
            if !free_select_policy.category_tags.contains(category_tag) {
                continue;
            }
            has_demanded_item |= category.item_ids.contains(&req.avatar_id);
        }
    }
    if !has_demanded_item {
        tracing::info!(
            "refuse free agent because: pool of parent_schedule_id {} doesn't have demanded item {}",
            req.gacha_parent_schedule_id, req.avatar_id
        );
        return Ok(GachaFreeAgentScRsp {
            retcode: Retcode::RetFail.into(),
            ..Default::default()
        });
    }

    role_model.add_avatar(AvatarBaseID::new(req.avatar_id).unwrap());
    (*status_bin
        .discount_usage_map
        .get_mut(&free_select_policy.free_select_usage_record_tag)
        .unwrap()) += 1;
    (*status_bin
        .discount_usage_map
        .get_mut(&free_select_policy.free_select_progress_record_tag)
        .unwrap()) -= free_select_required_pull;

    Ok(GachaFreeAgentScRsp {
        retcode: Retcode::RetSucc.into(),
    })
}

pub async fn on_choose_gacha_up(
    _session: &NetSession,
    _player: &mut Player,
    req: ChooseGachaUpCsReq,
) -> NetResult<ChooseGachaUpScRsp> {
    let gachaconf = global_gacha_config();
    let gacha_model = &mut _player.gacha_model;
    let pull_time = Local::now();
    let target_pool = get_gacha_pool(
        &gachaconf.character_gacha_pool_list,
        &req.gacha_parent_schedule_id,
        &pull_time,
    );
    if let None = target_pool {
        return Ok(ChooseGachaUpScRsp {
            retcode: Retcode::RetFail.into(),
            ..Default::default()
        });
    };
    let target_pool = target_pool.unwrap();

    for rarity_items in target_pool.gacha_items.iter() {
        if rarity_items.rarity != gachaconf.common_properties.s_item_rarity {
            continue;
        }
        for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
            let category_guarantee_policy = gachaconf
                .category_guarantee_policy_map
                .get(guarantee_policy_tag)
                .unwrap();
            if !category_guarantee_policy.chooseable {
                continue;
            }
            let mut up_category: Option<&String> = None;
            for (category_tag, category) in rarity_items.categories.iter() {
                if category.item_ids.contains(&req.item_id) {
                    up_category = Some(category_tag);
                    break;
                }
            }
            if let None = up_category {
                return Ok(ChooseGachaUpScRsp {
                    retcode: Retcode::RetFail.into(),
                    ..Default::default()
                });
            };
            let up_category = up_category.unwrap();

            let progress_bin = gacha_model
                .gacha_bin
                .gacha_status_map
                .get_mut(&target_pool.sharing_guarantee_info_category)
                .unwrap()
                .rarity_status_map
                .get_mut(&rarity_items.rarity)
                .unwrap();
            match progress_bin
                .categories_chosen_guarantee_item_map
                .entry(guarantee_policy_tag.clone())
            {
                Occupied(mut occupied_entry) => {
                    occupied_entry.insert(req.item_id);
                }
                Vacant(vacant_entry) => {
                    vacant_entry.insert(req.item_id);
                }
            };
            match progress_bin
                .categories_chosen_guarantee_category_map
                .entry(up_category.clone())
            {
                Occupied(mut occupied_entry) => {
                    occupied_entry.insert(up_category.clone());
                }
                Vacant(vacant_entry) => {
                    vacant_entry.insert(up_category.clone());
                }
            };
            return Ok(ChooseGachaUpScRsp {
                retcode: Retcode::RetSucc.into(),
                ..Default::default()
            });
        }
    }

    Ok(ChooseGachaUpScRsp {
        retcode: Retcode::RetFail.into(),
        ..Default::default()
    })
}
fn generate_gacha_info_from_pool(
    gacha_bin: &GachaModelBin,
    target_pool: &CharacterGachaPool,
    common_properties: &GachaCommonProperties,
) -> Gacha {
    let gachaconf = data::gacha::global_gacha_config();
    let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category;
    let status_bin = gacha_bin
        .gacha_status_map
        .get(sharing_guarantee_category_tag)
        .unwrap();
    let pity_s = status_bin
        .rarity_status_map
        .get(&common_properties.s_item_rarity)
        .unwrap()
        .pity;
    let pity_a = status_bin
        .rarity_status_map
        .get(&common_properties.a_item_rarity)
        .unwrap()
        .pity;

    let mut discount_ten_roll_prize: u32 = 0;
    let mut discount_avaliable_num: u32 = 0;
    let mut advanced_s_guarantee: u32 = 0;
    let mut free_select_progress: u32 = 0;
    let mut free_select_required_pull: u32 = 0;
    let mut free_select_policy: Option<&FreeSelectItem> = None;
    for discount_policy_tag in target_pool.discount_policy_tags.iter() {
        if common_properties.newcomer_advanced_s_tag == *discount_policy_tag {
            let policy = gachaconf
                .discount_policies
                .advanced_guarantee_map
                .get(discount_policy_tag)
                .unwrap();
            if status_bin
                .discount_usage_map
                .get(discount_policy_tag)
                .unwrap()
                < &policy.use_limit
            {
                advanced_s_guarantee = policy.guarantee_pity - pity_s + 1;
            }
        } else if common_properties.ten_pull_discount_tag == *discount_policy_tag {
            let policy = gachaconf
                .discount_policies
                .ten_pull_discount_map
                .get(discount_policy_tag)
                .unwrap();
            let discount_usage = status_bin
                .discount_usage_map
                .get(discount_policy_tag)
                .unwrap();
            if discount_usage < &policy.use_limit {
                discount_ten_roll_prize = policy.discounted_prize;
                discount_avaliable_num = policy.use_limit - discount_usage;
            }
        } else if gachaconf
            .discount_policies
            .free_select_map
            .contains_key(discount_policy_tag)
        {
            let policy = gachaconf
                .discount_policies
                .free_select_map
                .get(discount_policy_tag)
                .unwrap();
            let free_select_demand_idx = usize::try_from(
                *(status_bin
                    .discount_usage_map
                    .get(&policy.free_select_usage_record_tag)
                    .unwrap()),
            )
            .unwrap();
            if policy.milestones.len() <= free_select_demand_idx {
                continue;
            }

            let free_select_actual_progress = status_bin
                .discount_usage_map
                .get(&policy.free_select_progress_record_tag)
                .unwrap();
            free_select_policy = Some(policy);
            free_select_required_pull = policy
                .milestones
                .get(free_select_demand_idx)
                .unwrap()
                .to_owned();
            free_select_progress = min(free_select_required_pull, *free_select_actual_progress);
        }
    }

    let mut up_s_item_list: Vec<u32> = vec![];
    let mut up_a_item_list: Vec<u32> = vec![];
    let mut free_select_item_list: Vec<u32> = vec![];
    let mut chooseable_up_list: Vec<u32> = vec![];
    let mut chosen_up_item: u32 = 0;
    let mut s_guarantee: u32 = 0;
    let mut a_guarantee: u32 = 0;
    for rarity_items in target_pool.gacha_items.iter() {
        let mut chooseable_up_included_category_tags: Option<&HashSet<String>> = None;
        let mut chooseable_policy_tag: Option<&String> = None;
        for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
            let category_guarantee_policy = gachaconf
                .category_guarantee_policy_map
                .get(guarantee_policy_tag)
                .unwrap();
            if !category_guarantee_policy.chooseable {
                continue;
            }
            chooseable_policy_tag = Some(guarantee_policy_tag);
            chooseable_up_included_category_tags =
                Some(&category_guarantee_policy.included_category_tags);
            if let Some(item) = status_bin
                .rarity_status_map
                .get(&rarity_items.rarity)
                .unwrap()
                .categories_chosen_guarantee_item_map
                .get(guarantee_policy_tag)
            {
                chosen_up_item = item.clone();
            }
        }

        for (category_tag, category) in rarity_items.categories.iter() {
            let probability_model = gachaconf
                .probability_model_map
                .get(&rarity_items.probability_model_tag)
                .unwrap();
            let maximum_pity = &probability_model.maximum_guarantee_pity;
            if rarity_items.rarity == common_properties.s_item_rarity {
                if category.is_promotional_items {
                    up_s_item_list = category.item_ids.clone();
                }
                // tracing::info!("pity_s: {pity_s}");
                // thread 'tokio-runtime-worker' panicked at nap_gameserver\src\handlers\gacha.rs:369:31:
                // attempt to subtract with overflow
                s_guarantee = maximum_pity - min(pity_s, maximum_pity.clone()) + 1;
            }
            if rarity_items.rarity == common_properties.a_item_rarity {
                if category.is_promotional_items {
                    up_a_item_list = category.item_ids.clone();
                }
                // tracing::info!("pity_a: {pity_a}");
                a_guarantee = maximum_pity - min(pity_a, maximum_pity.clone()) + 1;
            }

            if let Some(val) = free_select_policy {
                if val.rarity == rarity_items.rarity && val.category_tags.contains(category_tag) {
                    free_select_item_list.append(&mut category.item_ids.clone());
                }
            }

            if let Some(tags) = chooseable_up_included_category_tags {
                if tags.contains(category_tag) {
                    chooseable_up_list.append(&mut category.item_ids.clone());
                }
            }
        }

        if let Some(_priority_policy_tag) = chooseable_policy_tag {
            // if let Some(item) = status_bin
            //     .rarity_status_map
            //     .get(&rarity_items.rarity)
            //     .unwrap()
            //     .categories_chosen_guarantee_item_map
            //     .get(priority_policy_tag)
            // {
            if rarity_items.rarity == gachaconf.common_properties.s_item_rarity {
                up_s_item_list = vec![];
            } else if rarity_items.rarity == gachaconf.common_properties.a_item_rarity {
                up_a_item_list = vec![];
            }
            // }
        }
    }

    let need_item_info_list: Vec<NeedItemInfo> = vec![NeedItemInfo {
        need_item_id: target_pool.cost_item_id,
        need_item_count: 1,
    }];

    let mut result = Gacha {
        gacha_schedule_id: target_pool.gacha_schedule_id,
        gacha_parent_schedule_id: target_pool.gacha_parent_schedule_id,
        gacha_type: target_pool.gacha_type,
        start_timestamp: target_pool.start_time.timestamp(),
        end_timestamp: target_pool.end_time.timestamp(),
        discount_avaliable_num,
        discount_ten_roll_prize,
        advanced_s_guarantee,
        s_guarantee,
        a_guarantee,
        need_item_info_list,
        free_select_progress,
        free_select_required_pull,
        free_select_item_list,
        chosen_up_item,
        // nammdglepbk: 563,
        // hgmcofcjmbg: 101,
        // akggbhgkifd: chooseable_up_list.clone(),
        chooseable_up_list,
        ..Gacha::default()
    };
    if up_s_item_list.len() > 0 {
        result.up_s_item_list = up_s_item_list;
    }
    if up_a_item_list.len() > 0 {
        result.up_a_item_list = up_a_item_list;
    }
    result
}

fn generate_all_gacha_info(_player: &Player, now: &DateTime<Local>) -> GachaData {
    let gachaconf = global_gacha_config();
    let gacha_bin = &_player.gacha_model.gacha_bin;
    let mut gacha_list: Vec<Gacha> = vec![];
    for target_pool in gachaconf.character_gacha_pool_list.iter() {
        if target_pool.is_still_open(now) {
            gacha_list.push(generate_gacha_info_from_pool(
                &gacha_bin,
                target_pool,
                &gachaconf.common_properties,
            ));
        }
    }

    // tracing::info!("gacha_list: {:?}", gacha_list);
    GachaData {
        random_number: 6167,
        gacha_pool: Some(GachaPool { gacha_list }),
        ..GachaData::default()
    }
}

fn get_gacha_pool<'conf>(
    character_gacha_pool_list: &'conf Vec<CharacterGachaPool>,
    gacha_parent_schedule_id: &u32,
    pull_time: &DateTime<Local>,
) -> Option<&'conf CharacterGachaPool> {
    for target_pool in character_gacha_pool_list.iter() {
        if &target_pool.gacha_parent_schedule_id == gacha_parent_schedule_id
            && target_pool.is_still_open(pull_time)
        {
            return Some(target_pool);
        }
    }
    None
}
