use std::collections::HashMap;

use trigger_fileconfig::{
    AvatarBattleTemplate, AvatarLevelAdvanceTemplate, AvatarPassiveSkillTemplate, NapFileCfg,
    WeaponLevelTemplate, WeaponStarTemplate, WeaponTemplate,
};
use trigger_logic::{battle::EPropertyType, skill::EAvatarSkillType};
use trigger_protocol::{Avatar, DungeonEquipInfo, Equip, Weapon};

struct AvatarFileCfg<'a> {
    pub avatar_battle_template: AvatarBattleTemplate<'a>,
    pub avatar_passive_skill_templates: Vec<AvatarPassiveSkillTemplate<'a>>,
    pub avatar_level_advances: Vec<AvatarLevelAdvanceTemplate<'a>>,
}

struct WeaponFileCfg<'a> {
    pub weapon_template: WeaponTemplate<'a>,
    pub weapon_level_template: WeaponLevelTemplate<'a>,
    pub weapon_star_template: WeaponStarTemplate<'a>,
}

const BEN_AVATAR_ID: u32 = 1121;
const BEN_CORE_PASSIVE_PERCENTAGE: [i32; 7] = [40, 46, 52, 60, 66, 72, 80];

pub fn calculate_for_avatar(
    avatar_id: u32,
    filecfg: &'static NapFileCfg,
    dungeon_equip: &DungeonEquipInfo,
) -> HashMap<u32, i32> {
    let mut calculated_properties = HashMap::<_, _>::new();
    let Some(player_avatar) = dungeon_equip
        .avatar_list
        .iter()
        .find(|a| (*a).id == avatar_id)
    else {
        return calculated_properties;
    };
    let Some(avatar_file_cfg) = get_avatar_filecfg(avatar_id, filecfg) else {
        return calculated_properties;
    };
    insert_avatar_battle_template_properties(&avatar_file_cfg, &mut calculated_properties);
    insert_avatar_battle_level_advance_properties(
        &avatar_file_cfg,
        player_avatar,
        &mut calculated_properties,
    );
    perform_avatar_growth_promotion_calculations(player_avatar, &mut calculated_properties);
    add_avatar_passive_skill_properties(
        &avatar_file_cfg,
        player_avatar,
        &mut calculated_properties,
    );
    let player_weapon = dungeon_equip
        .weapon_list
        .iter()
        .find(|w| w.uid == player_avatar.cur_weapon_uid);
    if player_weapon.is_some() {
        match get_weapon_filecfg(player_weapon.unwrap(), filecfg) {
            Some(weapon_file_cfg) => {
                add_weapon_template_properties(&weapon_file_cfg, &mut calculated_properties);
            }
            None => {}
        };
    }
    let player_dressed_equip_uids: Vec<u32> = player_avatar
        .dressed_equip_list
        .iter()
        .map(|de| de.equip_uid)
        .collect();
    let player_equips: Vec<&Equip> = dungeon_equip
        .equip_list
        .iter()
        .filter(|e| player_dressed_equip_uids.contains(&e.uid))
        .collect();
    if player_equips.len() > 0 {
        add_player_equip_properties(&player_equips, filecfg, &mut calculated_properties);
        add_equipment_suit_properties(&player_equips, filecfg, &mut calculated_properties);
    }
    calculate_final_properties(&mut calculated_properties);
    add_ben_core_passive(avatar_id, player_avatar, &mut calculated_properties);
    remove_custom_properties(&mut calculated_properties);
    set_battle_properties(&mut calculated_properties);

    calculated_properties
}

fn get_avatar_filecfg(
    avatar_id: u32,
    filecfg: &'static NapFileCfg,
) -> Option<AvatarFileCfg<'static>> {
    let Some(avatar_battle_template) = filecfg
        .avatar_battle_template_tb()
        .data()
        .unwrap()
        .iter()
        .find(|tmpl| tmpl.id() == avatar_id as i32)
    else {
        return None;
    };

    let avatar_passive_skill_templates: Vec<AvatarPassiveSkillTemplate> = filecfg
        .avatar_passive_skill_template_tb()
        .data()
        .unwrap()
        .iter()
        .filter(|tmpl| tmpl.avatar_id() == avatar_id as i32)
        .collect();
    if avatar_passive_skill_templates.len() == 0 {
        return None;
    }

    let avatar_level_advances: Vec<AvatarLevelAdvanceTemplate> = filecfg
        .avatar_level_advance_template_tb()
        .data()
        .unwrap()
        .iter()
        .filter(|tmpl| tmpl.avatar_id() == avatar_id as i32)
        .collect();
    if avatar_level_advances.len() == 0 {
        return None;
    }

    Some(AvatarFileCfg {
        avatar_battle_template,
        avatar_passive_skill_templates,
        avatar_level_advances,
    })
}

fn insert_avatar_battle_template_properties(
    avatar_file_cfg: &AvatarFileCfg,
    properties: &mut HashMap<u32, i32>,
) {
    properties.insert(
        EPropertyType::HpMaxBase.into(),
        avatar_file_cfg.avatar_battle_template.hp_max(),
    );
    properties.insert(
        EPropertyType::HpMaxGrowth.into(),
        avatar_file_cfg.avatar_battle_template.health_growth(),
    );
    properties.insert(
        EPropertyType::AtkBase.into(),
        avatar_file_cfg.avatar_battle_template.attack(),
    );
    properties.insert(
        EPropertyType::AtkGrowth.into(),
        avatar_file_cfg.avatar_battle_template.attack_growth(),
    );
    properties.insert(
        EPropertyType::BreakStunBase.into(),
        avatar_file_cfg.avatar_battle_template.break_stun(),
    );
    properties.insert(
        EPropertyType::DefBase.into(),
        avatar_file_cfg.avatar_battle_template.defence(),
    );
    properties.insert(
        EPropertyType::DefGrowth.into(),
        avatar_file_cfg.avatar_battle_template.defence_growth(),
    );
    properties.insert(
        EPropertyType::CritBase.into(),
        avatar_file_cfg.avatar_battle_template.crit(),
    );
    properties.insert(
        EPropertyType::CritDmgBase.into(),
        avatar_file_cfg.avatar_battle_template.crit_damage(),
    );
    properties.insert(
        EPropertyType::PenBase.into(),
        avatar_file_cfg.avatar_battle_template.pen_rate(),
    );
    properties.insert(
        EPropertyType::PenValueBase.into(),
        avatar_file_cfg.avatar_battle_template.pen_delta(),
    );
    properties.insert(
        EPropertyType::SpRecoverBase.into(),
        avatar_file_cfg.avatar_battle_template.sp_recover(),
    );
    properties.insert(
        EPropertyType::ElementMysteryBase.into(),
        avatar_file_cfg.avatar_battle_template.element_mystery(),
    );
    properties.insert(
        EPropertyType::ElementAbnormalPowerBase.into(),
        avatar_file_cfg
            .avatar_battle_template
            .element_abnormal_power(),
    );
}

fn insert_avatar_battle_level_advance_properties(
    avatar_file_cfg: &AvatarFileCfg,
    player_avatar: &Avatar,
    properties: &mut HashMap<u32, i32>,
) {
    let Some(avatar_level_advance) = avatar_file_cfg
        .avatar_level_advances
        .iter()
        .find(|tmpl| tmpl.id() == player_avatar.rank as i32)
    else {
        properties.insert(EPropertyType::HpMaxAdvance.into(), 0);
        properties.insert(EPropertyType::AtkAdvance.into(), 0);
        properties.insert(EPropertyType::DefAdvance.into(), 0);

        return;
    };

    properties.insert(
        EPropertyType::HpMaxAdvance.into(),
        avatar_level_advance.hp_max(),
    );
    properties.insert(
        EPropertyType::AtkAdvance.into(),
        avatar_level_advance.attack(),
    );
    properties.insert(
        EPropertyType::DefAdvance.into(),
        avatar_level_advance.defence(),
    );
}

fn perform_avatar_growth_promotion_calculations(
    player_avatar: &Avatar,
    properties: &mut HashMap<u32, i32>,
) {
    properties.insert(
        EPropertyType::HpMaxBase.into(),
        properties.get(&EPropertyType::HpMaxBase.into()).unwrap()
            + (((player_avatar.level - 1) as f32
                * *properties.get(&EPropertyType::HpMaxGrowth.into()).unwrap() as f32)
                / 10000f32) as i32
            + properties.get(&EPropertyType::HpMaxAdvance.into()).unwrap(),
    );
    properties.insert(
        EPropertyType::AtkBase.into(),
        properties.get(&EPropertyType::AtkBase.into()).unwrap()
            + (((player_avatar.level - 1) as f32
                * *properties.get(&EPropertyType::AtkGrowth.into()).unwrap() as f32)
                / 10000f32) as i32
            + properties.get(&EPropertyType::AtkAdvance.into()).unwrap(),
    );
    properties.insert(
        EPropertyType::DefBase.into(),
        properties.get(&EPropertyType::DefBase.into()).unwrap()
            + (((player_avatar.level - 1) as f32
                * *properties.get(&EPropertyType::DefGrowth.into()).unwrap() as f32)
                / 10000f32) as i32
            + properties.get(&EPropertyType::DefAdvance.into()).unwrap(),
    );
}

fn add_avatar_passive_skill_properties(
    avatar_file_cfg: &AvatarFileCfg,
    player_avatar: &Avatar,
    properties: &mut HashMap<u32, i32>,
) {
    let passive_skill_level = player_avatar
        .skill_type_level
        .iter()
        .find(|s| s.skill_type == EAvatarSkillType::CoreSkill as u32)
        .unwrap()
        .level;

    avatar_file_cfg
        .avatar_passive_skill_templates
        .iter()
        .find(|a| a.unlock_passive_skill_level() == passive_skill_level as i32)
        .map(|tmpl| tmpl.propertys().unwrap())
        .unwrap()
        .iter()
        .for_each(|p| {
            properties.insert(
                p.property() as u32,
                properties.get(&(p.property() as u32)).unwrap_or(&0) + p.value(),
            );
        });
}

fn get_weapon_filecfg(
    player_weapon: &Weapon,
    filecfg: &'static NapFileCfg,
) -> Option<WeaponFileCfg<'static>> {
    let Some(weapon_template) = filecfg
        .weapon_template_tb()
        .data()
        .unwrap()
        .iter()
        .find(|tmpl| tmpl.item_id() == player_weapon.id as i32)
    else {
        return None;
    };

    let Some(weapon_level_template) = filecfg
        .weapon_level_template_tb()
        .data()
        .unwrap()
        .iter()
        .find(|tmpl| tmpl.level() == player_weapon.level as i32)
    else {
        return None;
    };

    let Some(weapon_star_template) = filecfg
        .weapon_star_template_tb()
        .data()
        .unwrap()
        .iter()
        .find(|tmpl| tmpl.star() == player_weapon.star as i32)
    else {
        return None;
    };

    Some(WeaponFileCfg {
        weapon_template,
        weapon_level_template,
        weapon_star_template,
    })
}

fn add_weapon_template_properties(
    weapon_file_cfg: &WeaponFileCfg,
    properties: &mut HashMap<u32, i32>,
) {
    let base_property = weapon_file_cfg.weapon_template.base_property().unwrap();
    let base_property_value = base_property.value()
        + (base_property.value() as f32 * weapon_file_cfg.weapon_level_template.rate() as f32
            / 10000f32) as i32
        + (base_property.value() as f32 * weapon_file_cfg.weapon_star_template.star_rate() as f32
            / 10000f32) as i32;
    properties.insert(
        base_property.property() as u32,
        *properties
            .get(&(base_property.property() as u32))
            .unwrap_or(&0)
            + base_property_value,
    );
    let rand_property = weapon_file_cfg.weapon_template.rand_property().unwrap();
    let rand_property_value = rand_property.value()
        + (rand_property.value() as f32 * weapon_file_cfg.weapon_star_template.rand_rate() as f32
            / 10000f32) as i32;
    properties.insert(
        rand_property.property() as u32,
        *properties
            .get(&(rand_property.property() as u32))
            .unwrap_or(&0)
            + rand_property_value,
    );
}

fn add_player_equip_properties(
    player_equips: &Vec<&Equip>,
    filecfg: &'static NapFileCfg,
    properties: &mut HashMap<u32, i32>,
) {
    player_equips.iter().for_each(|pe| {
        let equip_rarity = (pe.id / 10) % 10;
        let property_rate = filecfg
            .equipment_level_template_tb()
            .data()
            .unwrap()
            .iter()
            .find(|tmpl| tmpl.rarity() == equip_rarity as i32 && tmpl.level() == pe.level as i32)
            .map(|tmpl| tmpl.property_rate())
            .unwrap_or(1);
        pe.propertys.iter().for_each(|p| {
            properties.insert(
                p.key,
                properties.get(&p.key).unwrap_or(&0)
                    + p.base_value as i32
                    + (p.base_value as f32 * property_rate as f32 / 10000f32) as i32,
            );
        });
        pe.sub_propertys.iter().for_each(|p| {
            properties.insert(
                p.key,
                properties.get(&p.key).unwrap_or(&0)
                    + (p.base_value as f32 * p.add_value as f32) as i32,
            );
        });
    });
}

fn add_equipment_suit_properties(
    player_equips: &Vec<&Equip>,
    filecfg: &'static NapFileCfg,
    properties: &mut HashMap<u32, i32>,
) {
    let mut suit_counts = HashMap::<i32, i32>::new();
    player_equips.iter().for_each(|pe| {
        let suit_id = pe.id as i32 / 100 * 100;
        suit_counts.insert(suit_id, *suit_counts.get(&suit_id).unwrap_or(&0) + 1);
    });
    suit_counts.iter().for_each(|sc| {
        let Some(equipment_suit_template) = filecfg
            .equipment_suit_template_tb()
            .data()
            .unwrap()
            .iter()
            .find(|tmpl| tmpl.id() == *sc.0)
        else {
            return;
        };
        if equipment_suit_template.primary_condition() <= *sc.1 {
            equipment_suit_template
                .primary_suit_propertys()
                .unwrap()
                .iter()
                .for_each(|p| {
                    properties.insert(
                        p.property() as u32,
                        properties.get(&(p.property() as u32)).unwrap_or(&0) + p.value(),
                    );
                });
        }
    });
}

fn calculate_final_properties(properties: &mut HashMap<u32, i32>) {
    properties.insert(
        EPropertyType::HpMax.into(),
        properties.get(&EPropertyType::HpMaxBase.into()).unwrap()
            + (*properties.get(&EPropertyType::HpMaxBase.into()).unwrap() as f32
                * *properties
                    .get(&EPropertyType::HpMaxRatio.into())
                    .unwrap_or(&0) as f32
                / 10000f32)
                .ceil() as i32
            + properties
                .get(&EPropertyType::HpMaxDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::Atk.into(),
        properties.get(&EPropertyType::AtkBase.into()).unwrap()
            + (*properties.get(&EPropertyType::AtkBase.into()).unwrap() as f32
                * *properties
                    .get(&EPropertyType::AtkRatio.into())
                    .unwrap_or(&0) as f32
                / 10000f32) as i32
            + properties
                .get(&EPropertyType::AtkDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::BreakStun.into(),
        properties
            .get(&EPropertyType::BreakStunBase.into())
            .unwrap()
            + (*properties
                .get(&EPropertyType::BreakStunBase.into())
                .unwrap() as f32
                * *properties
                    .get(&EPropertyType::BreakStunRatio.into())
                    .unwrap_or(&0) as f32
                / 10000f32) as i32
            + properties
                .get(&EPropertyType::BreakStunDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::Def.into(),
        properties.get(&EPropertyType::DefBase.into()).unwrap()
            + (*properties.get(&EPropertyType::DefBase.into()).unwrap() as f32
                * *properties
                    .get(&EPropertyType::DefRatio.into())
                    .unwrap_or(&0) as f32
                / 10000f32) as i32
            + properties
                .get(&EPropertyType::DefDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::Crit.into(),
        properties.get(&EPropertyType::CritBase.into()).unwrap()
            + properties
                .get(&EPropertyType::CritDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::CritDmg.into(),
        properties.get(&EPropertyType::CritDmgBase.into()).unwrap()
            + properties
                .get(&EPropertyType::CritDmgDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::Pen.into(),
        properties.get(&EPropertyType::PenBase.into()).unwrap()
            + properties
                .get(&EPropertyType::PenDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::PenValue.into(),
        properties.get(&EPropertyType::PenValueBase.into()).unwrap()
            + properties
                .get(&EPropertyType::PenValueDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::SpRecover.into(),
        properties
            .get(&EPropertyType::SpRecoverBase.into())
            .unwrap()
            + (*properties
                .get(&EPropertyType::SpRecoverBase.into())
                .unwrap() as f32
                * *properties
                    .get(&EPropertyType::SpRecoverRatio.into())
                    .unwrap_or(&0) as f32
                / 10000f32) as i32
            + properties
                .get(&EPropertyType::SpRecoverDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::ElementMystery.into(),
        properties
            .get(&EPropertyType::ElementMysteryBase.into())
            .unwrap()
            + properties
                .get(&EPropertyType::ElementMysteryDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::ElementAbnormalPower.into(),
        properties
            .get(&EPropertyType::ElementAbnormalPowerBase.into())
            .unwrap()
            + (*properties
                .get(&EPropertyType::ElementAbnormalPowerBase.into())
                .unwrap() as f32
                * *properties
                    .get(&EPropertyType::ElementAbnormalPowerRatio.into())
                    .unwrap_or(&0) as f32
                / 10000f32) as i32
            + properties
                .get(&EPropertyType::ElementAbnormalPowerDelta.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioPhysics.into(),
        properties
            .get(&EPropertyType::AddedDamageRatioPhysics1.into())
            .unwrap_or(&0)
            + properties
                .get(&EPropertyType::AddedDamageRatioPhysics3.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioFire.into(),
        properties
            .get(&EPropertyType::AddedDamageRatioFire1.into())
            .unwrap_or(&0)
            + properties
                .get(&EPropertyType::AddedDamageRatioFire3.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioIce.into(),
        properties
            .get(&EPropertyType::AddedDamageRatioIce1.into())
            .unwrap_or(&0)
            + properties
                .get(&EPropertyType::AddedDamageRatioIce3.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioElec.into(),
        properties
            .get(&EPropertyType::AddedDamageRatioElec1.into())
            .unwrap_or(&0)
            + properties
                .get(&EPropertyType::AddedDamageRatioElec3.into())
                .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioEther.into(),
        properties
            .get(&EPropertyType::AddedDamageRatioEther1.into())
            .unwrap_or(&0)
            + properties
                .get(&EPropertyType::AddedDamageRatioEther3.into())
                .unwrap_or(&0),
    );
}

fn add_ben_core_passive(
    avatar_id: u32,
    player_avatar: &Avatar,
    properties: &mut HashMap<u32, i32>,
) {
    if avatar_id != BEN_AVATAR_ID {
        return;
    }
    let core_level = player_avatar
        .skill_type_level
        .iter()
        .find(|sl| sl.skill_type == EAvatarSkillType::CoreSkill as u32)
        .unwrap()
        .level as usize;
    let def_atk_bonus = match core_level {
        1..=7 => {
            *properties.get(&EPropertyType::Def.into()).unwrap_or(&0)
                * BEN_CORE_PASSIVE_PERCENTAGE[core_level - 1]
                / 100
        }
        _ => 0,
    };
    properties.insert(
        EPropertyType::Atk.into(),
        *properties.get(&EPropertyType::Atk.into()).unwrap_or(&0) + def_atk_bonus,
    );
}

fn remove_custom_properties(properties: &mut HashMap<u32, i32>) {
    properties.remove(&EPropertyType::HpMaxGrowth.into());
    properties.remove(&EPropertyType::AtkGrowth.into());
    properties.remove(&EPropertyType::DefGrowth.into());
    properties.remove(&EPropertyType::HpMaxAdvance.into());
    properties.remove(&EPropertyType::AtkAdvance.into());
    properties.remove(&EPropertyType::DefAdvance.into());
}

fn set_battle_properties(properties: &mut HashMap<u32, i32>) {
    properties.insert(
        EPropertyType::Hp.into(),
        *properties.get(&EPropertyType::HpMax.into()).unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::HpMaxBattle.into(),
        *properties.get(&EPropertyType::HpMax.into()).unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AtkBattle.into(),
        *properties.get(&EPropertyType::Atk.into()).unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::BreakStunBattle.into(),
        *properties
            .get(&EPropertyType::BreakStun.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::DefBattle.into(),
        *properties.get(&EPropertyType::Def.into()).unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::CritBattle.into(),
        *properties.get(&EPropertyType::Crit.into()).unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::CritDmgBattle.into(),
        *properties.get(&EPropertyType::CritDmg.into()).unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::PenRatioBattle.into(),
        *properties.get(&EPropertyType::Pen.into()).unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::PenDeltaBattle.into(),
        *properties
            .get(&EPropertyType::PenValue.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::SpRecoverBattle.into(),
        *properties
            .get(&EPropertyType::SpRecover.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::ElementMysteryBattle.into(),
        *properties
            .get(&EPropertyType::ElementMystery.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::ElementAbnormalPowerBattle.into(),
        *properties
            .get(&EPropertyType::ElementAbnormalPower.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioPhysicsBattle.into(),
        *properties
            .get(&EPropertyType::AddedDamageRatioPhysics.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioFireBattle.into(),
        *properties
            .get(&EPropertyType::AddedDamageRatioFire.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioIceBattle.into(),
        *properties
            .get(&EPropertyType::AddedDamageRatioIce.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioElecBattle.into(),
        *properties
            .get(&EPropertyType::AddedDamageRatioElec.into())
            .unwrap_or(&0),
    );
    properties.insert(
        EPropertyType::AddedDamageRatioEtherBattle.into(),
        *properties
            .get(&EPropertyType::AddedDamageRatioEther.into())
            .unwrap_or(&0),
    );
}
