use serde::{Deserialize, Serialize};

use crate::dnd::{classes::AbilityType, conditions::ConditionType};

use super::{
  SchoolType, CastingTime, SpellAttackType, SpellDamageType, Range, Area, Components, Duration,
  Source, Description, DurationType, Effect,
};

#[derive(Debug, Serialize, Deserialize)]
pub struct QuerySpell {
  pub id: i32,
  pub name: String,
  pub school: String,
  pub level: i32,
  pub ritual: bool,
  pub concentration: bool,
  pub classes: Vec<String>,
  pub damage_inflict: Vec<String>,
  pub damage_resist: Vec<String>,
  pub conditions: Vec<String>,
  pub saving_throw: Vec<String>,
  pub attack_type: Option<String>,
  pub data: serde_json::Value,
}

#[derive(Debug)]
pub struct InsertSpell {
  pub name: String,
  pub school: String,
  pub level: i32,
  pub ritual: bool,
  pub concentration: bool,
  pub classes: Vec<String>,
  pub damage_inflict: Vec<String>,
  pub damage_resist: Vec<String>,
  pub conditions: Vec<String>,
  pub saving_throw: Vec<String>,
  pub attack_type: Option<String>,
  pub data: serde_json::Value,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Spell {
  pub id: Option<i32>,
  pub name: String,
  pub school: SchoolType,
  pub level: i32,
  pub ritual: bool,
  pub casting_time: CastingTime,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub effect: Option<Effect>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub saving_throw: Option<Vec<AbilityType>>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub attack_type: Option<SpellAttackType>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub damage_inflict: Option<Vec<SpellDamageType>>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub damage_resist: Option<Vec<SpellDamageType>>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub conditions: Option<Vec<ConditionType>>,
  pub range: Range,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub area: Option<Area>,
  pub components: Components,
  pub durations: Vec<Duration>,
  pub classes: Vec<String>,
  pub sources: Vec<Source>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub tags: Option<Vec<String>>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub description: Option<Description>,
}

impl From<QuerySpell> for Spell {
  fn from(query: QuerySpell) -> Self {
    return match serde_json::from_value(query.data) {
      Ok(data) => data,
      Err(err) => {
        log::error!("Failed to parse spell: {}", err);
        Self {
          id: None,
          name: "".to_string(),
          school: SchoolType::Abjuration,
          level: 0,
          ritual: false,
          casting_time: CastingTime {
            value: 0,
            casting_type: "".to_string(),
            note: None,
          },
          effect: None,
          saving_throw: None,
          attack_type: None,
          damage_inflict: None,
          damage_resist: None,
          conditions: None,
          range: Range {
            range_type: "".to_string(),
            value: None,
            unit: None,
          },
          area: None,
          components: Components {
            verbal: false,
            somatic: false,
            material: false,
            materials_needed: None,
            materials_cost: None,
            materials_consumed: None,
          },
          durations: vec![],
          classes: vec![],
          sources: vec![],
          tags: None,
          description: None,
        }
      }
    };
  }
}

impl Into<InsertSpell> for Spell {
  fn into(self) -> InsertSpell {
    return InsertSpell {
      name: self.name.to_string(),
      school: self.school.to_string(),
      level: self.level,
      ritual: self.ritual,
      concentration: self
        .durations
        .iter()
        .any(|duration| match duration.duration_type {
          DurationType::Concentration => true,
          _ => false,
        }),
      classes: self
        .classes
        .iter()
        .map(|class| class.to_string())
        .collect::<Vec<String>>(),
      damage_inflict: match &self.damage_inflict {
        Some(damage_inflict) => damage_inflict
          .iter()
          .map(|damage_inflict| damage_inflict.to_string())
          .collect(),
        None => vec![],
      },
      damage_resist: match &self.damage_resist {
        Some(damage_resist) => damage_resist
          .iter()
          .map(|damage_resist| damage_resist.to_string())
          .collect(),
        None => vec![],
      },
      conditions: match &self.conditions {
        Some(conditions) => conditions
          .iter()
          .map(|condition| condition.to_string())
          .collect(),
        None => vec![],
      },
      saving_throw: match &self.saving_throw {
        Some(saving_throw) => saving_throw
          .iter()
          .map(|saving_throw| saving_throw.to_string())
          .collect(),
        None => vec![],
      },
      attack_type: self
        .attack_type
        .as_ref()
        .map(|attack_type| attack_type.to_string()),
      data: match serde_json::to_value(&self) {
        Ok(data) => data,
        Err(err) => {
          log::error!("Failed to serialize spell: {}", err);
          serde_json::Value::Null
        }
      },
    };
  }
}
