use anyhow::Result;
use assetpath::AssetPath;
use cgmath::{Vector2, Vector3};
use engine::prelude::*;

use std::{fmt::Debug, str::FromStr, sync::Arc, time::Duration};

use crate::{
    components::{
        character_status::CharacterStatus, crafting_materials::CraftingMaterials,
        inventory::Storable, statistics::Statistics,
    },
    config::abilities::{AbilityLevel, AbilitySettings},
    damage_type::DamageType,
};

use super::{
    ItemSystem, Rarities, Tooltip,
    ability_addon::{AbilityAddon, AbilityAddonCollection, AbilityAddonTypes},
};

pub trait Ability: Send + Sync + Clone + Default {
    fn create(context: &Context, asset_path: impl Into<AssetPath>) -> Result<Self>;

    fn name(&self) -> &str;
    fn icon_path(&self) -> &AssetPath;

    fn cool_down(&self) -> Duration;
    fn mana_cost(&self) -> u32;
    fn mana_cost_per_level(&self) -> u32;
    fn damage_type(&self) -> DamageType;
    fn base_damage(&self) -> u32;
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CastInformation {
    time: PersistentDuration,
    location: Vector3<f32>,
    direction: Vector2<f32>,
}

#[derive(Clone)]
pub struct AbilityBook<A: Ability> {
    ability: A,

    // meta
    icon: Arc<Image>,
    rarity: Rarities,

    // addons
    addons: AbilityAddonCollection,

    level: u32,
    ability_level_settings: AbilityLevel,

    // cool down
    last_cast: Option<CastInformation>,
}

impl<A: Ability> AbilityBook<A> {
    pub fn new(
        ability: A,
        icon: Arc<Image>,
        rarity: Rarities,
        ability_settings: &AbilitySettings,
    ) -> Self {
        AbilityBook {
            ability,

            // meta
            icon,
            rarity,

            // addons
            addons: AbilityAddonCollection::new(rarity, ability_settings),

            level: 1,
            ability_level_settings: ability_settings.level.clone(),

            // cool down
            last_cast: None,
        }
    }

    pub fn load(
        ability: A,
        icon: Arc<Image>,
        rarity: Rarities,
        addons: Vec<Option<AbilityAddon>>,
        ability_settings: &AbilitySettings,
        level: u32,
    ) -> Self {
        AbilityBook {
            ability,

            icon,
            rarity,

            addons: AbilityAddonCollection::load(addons, rarity, ability_settings),

            level,
            ability_level_settings: ability_settings.level.clone(),

            last_cast: None,
        }
    }

    pub fn check_mana(&self, character_status: &mut CharacterStatus) -> Result<bool> {
        let mana_costs = self.mana_cost();

        Ok(character_status.use_ability(mana_costs as f32))
    }

    #[allow(clippy::too_many_arguments)]
    pub fn validate_use(
        &mut self,
        now: Duration,
        character_status: &mut CharacterStatus,
        location: &Location,
    ) -> Result<bool> {
        // don't allow anything while being animation locked

        if let Some(cast_information) = &self.last_cast {
            let total_cool_down = Duration::from_secs_f32({
                let d = self.ability.cool_down();

                d.as_secs_f32() * (1.0 - self.addons.cool_down_reduction())
            });

            if (now - cast_information.time.into()) <= total_cool_down {
                return Ok(false);
            }
        }

        if !self.check_mana(character_status)? {
            return Ok(false);
        }

        // {
        //     // TODO: further separation of animation types (bows, ...)
        //     let animation_type = match self.ability.data().settings.parameter.damage_type {
        //         DamageType::Physical => AnimationType::Attack,
        //         _ => AnimationType::Cast,
        //     };

        //     animation_info.set_animation(
        //         animation,
        //         draw,
        //         Some(animation_type),
        //         now,
        //         true,
        //         false,
        //     )?;
        // }

        self.set_cast(now, location);

        Ok(true)
    }

    fn set_cast(&mut self, now: Duration, location: &Location) {
        self.last_cast = Some(CastInformation {
            time: now.into(),
            location: location.position(),
            direction: location.direction(),
        });
    }

    pub fn last_cast(&self) -> &Option<CastInformation> {
        &self.last_cast
    }

    pub fn check_cool_down(&mut self, now: Duration) -> Option<f32> {
        match &self.last_cast {
            Some(cast_information) => {
                let total_cool_down = Duration::from_secs_f32(
                    self.ability.cool_down().as_secs_f32()
                        * (1.0 - self.addons.cool_down_reduction()),
                );

                let diff = now - cast_information.time.into();

                if diff <= total_cool_down {
                    Some((total_cool_down - diff).as_secs_f32())
                } else {
                    self.last_cast = None;

                    None
                }
            }
            None => None,
        }
    }

    pub fn ability(&self) -> &A {
        &self.ability
    }

    pub fn ability_mut(&mut self) -> &mut A {
        &mut self.ability
    }

    pub fn addons(&self) -> &AbilityAddonCollection {
        &self.addons
    }

    pub fn addons_mut(&mut self) -> &mut AbilityAddonCollection {
        &mut self.addons
    }

    pub fn has_free_addon_slots(&self) -> bool {
        self.addons.has_free_addon_slots()
    }

    pub fn attached_count(&self) -> usize {
        self.addons.attached_count()
    }

    pub fn level(&self) -> u32 {
        self.level
    }

    pub fn upgrade_cost(&self) -> u32 {
        self.ability_level_settings.starting_cost
            + (self.level - 1) * self.ability_level_settings.cost_per_level
    }

    pub fn upgrade(&mut self, crafting_materials: &mut CraftingMaterials) -> bool {
        if crafting_materials.consume(self.rarity, self.upgrade_cost()) {
            self.level += 1;

            true
        } else {
            false
        }
    }

    // only used at a copy for ability upgrade tool tip
    pub fn dummy_uprade(&mut self) {
        self.level += 1;
    }

    pub fn mana_cost(&self) -> u32 {
        self.ability.mana_cost() + self.ability.mana_cost_per_level() * (self.level - 1)
    }

    pub fn damage(&self, statistics: &Statistics) -> u32 {
        // calculate damage of base ability
        let ability_base_damage = self.ability.base_damage();

        // get bonus damage from statistics
        let stats_damage = match self.ability.damage_type() {
            DamageType::Air => statistics.air_damage.raw(),
            DamageType::Fire => statistics.fire_damage.raw(),
            DamageType::Water => statistics.water_damage.raw(),
            DamageType::Physical => statistics.physical_damage.raw(),
        };

        // damage from addons multiplied with level
        let addon_damage = self.addons.damage() * self.level;

        // sum up
        ability_base_damage + (stats_damage * self.level) + addon_damage
    }

    pub fn into_persistent(&self) -> String {
        let mut base = format!("{}|{}|{}", self.ability().name(), self.rarity(), self.level);

        for addon in self.addons.iter().flatten() {
            base = format!("{}|{}_{}", base, addon.addon_type(), addon.rarity());
        }

        base
    }

    pub fn from_persistent<'a>(
        mut split: impl Iterator<Item = &'a str>,
        item_system: &ItemSystem<A>,
    ) -> Result<Self> {
        let name = split.next().unwrap();
        let rarity = Rarities::from_str(split.next().unwrap())?;
        let level = u32::from_str(split.next().unwrap())?;

        let mut addons = Vec::new();

        for addon in split {
            let mut addon_split = addon.split('_');

            let addon_type = AbilityAddonTypes::from_str(addon_split.next().unwrap())?;
            let addon_rarity = Rarities::from_str(addon_split.next().unwrap())?;

            addons.push(Some(item_system.addon(addon_rarity, addon_type)));
        }

        Ok(item_system.ability_book(name, rarity, addons, level))
    }

    pub fn create_tooltip(
        &self,
        gui_handler: &Arc<GuiHandler>,
        statistics: &Statistics,
        position: (i32, i32),
    ) -> Result<Tooltip> {
        let gui = GuiBuilder::from_str(
            gui_handler,
            include_str!("../../resources/book_snippet.xml"),
        )?;

        let ability_name: Arc<Label> = gui.element("ability_name")?;
        let rarity_label: Arc<Label> = gui.element("rarity_label")?;
        let inspector_grid: Arc<Grid> = gui.element("item_grid")?;
        let ability_icon: Arc<Icon> = gui.element("ability_icon")?;
        let slot_info: Arc<Label> = gui.element("slot_info")?;
        let level: Arc<Label> = gui.element("level")?;

        inspector_grid.change_position_unscaled(position.0, position.1)?;

        ability_icon.set_icon(&self.icon())?;
        ability_name.set_text(self.ability().name())?;
        rarity_label.set_text(&format!("{}", self.rarity()))?;
        level.set_text(&format!("Lvl: {}", self.level()))?;

        slot_info.set_text(&format!(
            "Slots: {}/{}",
            self.attached_count(),
            self.addons().len()
        ))?;

        let mana_costs: Arc<Label> = gui.element("mana_costs")?;
        let damage: Arc<Label> = gui.element("damage")?;
        let cooldown: Arc<Label> = gui.element("cooldown")?;

        mana_costs.set_text(self.mana_cost())?;
        damage.set_text(self.damage(statistics))?;
        damage.set_text_color(self.ability.damage_type().into())?;
        cooldown.set_text(format!(
            "{:.1} s",
            self.ability.cool_down().as_secs_f32() * (1.0 - self.addons().cool_down_reduction())
        ))?;

        Ok(Tooltip::new(inspector_grid, gui, gui_handler.clone()))
    }
}

impl<A: Ability> Debug for AbilityBook<A> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AbilityBook")
            .field("rarity", &self.rarity)
            .field("attached_addons", &self.attached_count())
            .field("addons", &self.addons)
            .field("last_cast", &self.last_cast)
            .finish()
    }
}

impl<A: Ability> PartialEq for AbilityBook<A> {
    fn eq(&self, other: &Self) -> bool {
        self.ability.name() == other.ability.name()
            && self.rarity == other.rarity
            && self.addons == other.addons
            && self.level == other.level
    }
}

impl<A: Ability> Storable for AbilityBook<A> {
    fn rarity(&self) -> Rarities {
        self.rarity
    }

    fn icon(&self) -> Arc<Image> {
        self.icon.clone()
    }
}