use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use siren::ServiceError;

use crate::storage::connection;
use crate::storage::schema::spells::{self};
use crate::dnd::{classes::AbilityType, conditions::ConditionType};

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

#[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)]
#[diesel(table_name = spells)]
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 QueryFilters {
  pub by_name: Option<String>,
  pub like_name: Option<String>,
  pub by_schools: Option<Vec<String>>,
  pub by_levels: Option<Vec<i32>>,
  pub by_ritual: Option<bool>,
  pub by_concentration: Option<bool>,
  pub by_classes: Option<Vec<String>>,
  pub by_damage_inflict: Option<Vec<String>>,
  pub by_damage_resist: Option<Vec<String>>,
  pub by_conditions: Option<Vec<String>>,
  pub by_saving_throw: Option<Vec<String>>,
  pub by_attack_type: Option<String>,
}

impl Default for QueryFilters {
  fn default() -> Self {
    Self {
      by_name: None,
      like_name: None,
      by_schools: None,
      by_levels: None,
      by_ritual: None,
      by_concentration: None,
      by_classes: None,
      by_damage_inflict: None,
      by_damage_resist: None,
      by_conditions: None,
      by_saving_throw: None,
      by_attack_type: None,
    }
  }
}

impl QuerySpell {
  pub fn get_all(filters: &QueryFilters, limit: i32, page: i32) -> Result<Vec<Self>, ServiceError> {
    let mut conn = connection()?;
    let mut query = spells::table.limit(limit as i64).into_boxed();
    // Limit query to page and limit
    let offset = (page - 1) * limit;
    query = query.offset(offset as i64);
    if let Some(name) = &filters.by_name {
      query = query.filter(spells::name.eq(name));
    }
    if let Some(name) = &filters.like_name {
      query = query.filter(spells::name.ilike(format!("%{}%", name)));
    }
    if let Some(schools) = &filters.by_schools {
      query = query.filter(spells::school.eq_any(schools.iter().map(|school| school.to_string()).collect::<Vec<String>>()));
    }
    if let Some(levels) = &filters.by_levels {
      query = query.filter(spells::level.eq_any(levels));
    }
    if let Some(ritual) = filters.by_ritual {
      query = query.filter(spells::ritual.eq(ritual));
    }
    if let Some(concentration) = filters.by_concentration {
      query = query.filter(spells::concentration.eq(concentration));
    }
    if let Some(classes) = &filters.by_classes {
      query = query.filter(spells::classes.overlaps_with(classes));
    }
    if let Some(damage_inflict) = &filters.by_damage_inflict {
      query = query.filter(spells::damage_inflict.overlaps_with(damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect::<Vec<String>>()));
    }
    if let Some(damage_resist) = &filters.by_damage_resist {
      query = query.filter(spells::damage_resist.overlaps_with(damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect::<Vec<String>>()));
    }
    if let Some(conditions) = &filters.by_conditions {
      query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::<Vec<String>>()));
    }
    if let Some(saving_throw) = &filters.by_saving_throw {
      query = query.filter(spells::saving_throw.overlaps_with(saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect::<Vec<String>>()));
    }
    if let Some(attack_type) = &filters.by_attack_type {
      query = query.filter(spells::attack_type.eq(attack_type.to_string()));
    }

    let spells = query.load::<QuerySpell>(&mut conn)?;
    Ok(spells)
  }

  pub fn get_count(filters: &QueryFilters) -> Result<i64, ServiceError> {
    let mut conn = connection()?;
    let mut query = spells::table.count().into_boxed();
    if let Some(name) = &filters.by_name {
      query = query.filter(spells::name.ilike(format!("%{}%", name)));
    }
    if let Some(schools) = &filters.by_schools {
      query = query.filter(spells::school.eq_any(schools.iter().map(|school| school.to_string()).collect::<Vec<String>>()));
    }
    if let Some(levels) = &filters.by_levels {
      query = query.filter(spells::level.eq_any(levels));
    }
    if let Some(ritual) = filters.by_ritual {
      query = query.filter(spells::ritual.eq(ritual));
    }
    if let Some(concentration) = filters.by_concentration {
      query = query.filter(spells::concentration.eq(concentration));
    }
    if let Some(classes) = &filters.by_classes {
      query = query.filter(spells::classes.overlaps_with(classes));
    }
    if let Some(damage_inflict) = &filters.by_damage_inflict {
      query = query.filter(spells::damage_inflict.overlaps_with(damage_inflict.iter().map(|damage_inflict| damage_inflict.to_string()).collect::<Vec<String>>()));
    }
    if let Some(damage_resist) = &filters.by_damage_resist {
      query = query.filter(spells::damage_resist.overlaps_with(damage_resist.iter().map(|damage_resist| damage_resist.to_string()).collect::<Vec<String>>()));
    }
    if let Some(conditions) = &filters.by_conditions {
      query = query.filter(spells::conditions.overlaps_with(conditions.iter().map(|condition| condition.to_string()).collect::<Vec<String>>()));
    }
    if let Some(saving_throw) = &filters.by_saving_throw {
      query = query.filter(spells::saving_throw.overlaps_with(saving_throw.iter().map(|saving_throw| saving_throw.to_string()).collect::<Vec<String>>()));
    }
    if let Some(attack_type) = &filters.by_attack_type {
      query = query.filter(spells::attack_type.eq(attack_type.to_string()));
    }
    
    let count = query.get_result(&mut conn)?;
    Ok(count)
  }

  pub fn get_by_id(id: i32) -> Result<Self, ServiceError> {
    let mut conn = connection()?;
    let spell = spells::table
      .filter(spells::id.eq(id))
      .first::<QuerySpell>(&mut conn)?;
    Ok(spell)
  }

  pub fn delete(id: i32) -> Result<Self, ServiceError> {
    let mut conn = connection()?;
    let spell = diesel::delete(spells::table.filter(spells::id.eq(id))).get_result(&mut conn)?;
    Ok(spell)
  }
}

#[derive(Debug, Insertable, AsChangeset)]
#[diesel(table_name = spells)]
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
}

impl InsertSpell {
  pub fn insert(spell: Self) -> Result<QuerySpell, ServiceError> {
    let mut conn = connection()?;
    let spell = diesel::insert_into(spells::table).values(spell).get_result(&mut conn)?;
    Ok(spell)
  }

  pub fn update(id: i32, spell: Self) -> Result<QuerySpell, ServiceError> {
    let mut conn = connection()?;
    let spell = diesel::update(spells::table.filter(spells::id.eq(id))).set(spell).get_result(&mut conn)?;
    Ok(spell)
  }
}

#[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
        }
      }
    }
  }
}
