use actix_web::{get, post, put, delete, web, HttpResponse, HttpRequest, ResponseError};
use log::error;
use serde::{Serialize, Deserialize};
use siren::{Response, Metadata, ServiceError};

use crate::{dnd::spells::{QuerySpell, QueryFilters}, auth::{JwtAuth, verify_role}};

use super::{Spell, InsertSpell};

#[derive(Serialize, Deserialize)]
struct GetAllParams {
  name: Option<String>,
  like_name: Option<String>,
  schools: Option<String>,
  levels: Option<String>,
  ritual: Option<bool>,
  concentration: Option<bool>,
  classes: Option<String>,
  damage_inflict: Option<String>,
  damage_resist: Option<String>,
  conditions: Option<String>,
  saving_throw: Option<String>,
  attack_type: Option<String>,
  limit: Option<i32>,
  page: Option<i32>,
}

#[get("/spells")]
async fn get_all(req: HttpRequest) -> HttpResponse {
  let params = match web::Query::<GetAllParams>::from_query(req.query_string()) {
    Ok(params) => params,
    Err(err) => return ResponseError::error_response(&ServiceError {
      status: 422,
      message: err.to_string()
    })
  };
  let mut filters = QueryFilters::default();
  filters.by_name = params.name.clone();
  filters.like_name = params.like_name.clone();
  filters.by_schools = match &params.schools {
    Some(schools) => Some(schools.split(",").map(|s| s.to_string()).collect()),
    None => None
  };
  filters.by_levels = match &params.levels {
    Some(levels) => Some(levels.split(",").map(|s| match s.to_string().parse::<i32>() {
      Ok(level) => level,
      Err(_) => 0
    }).collect()),
    None => None
  };
  filters.by_ritual = params.ritual;
  filters.by_concentration = params.concentration;
  filters.by_classes = match &params.classes {
    Some(classes) => Some(classes.split(",").map(|s| s.to_string()).collect()),
    None => None
  };
  filters.by_damage_inflict = match &params.damage_inflict {
    Some(damage_inflict) => Some(damage_inflict.split(",").map(|s| s.to_string()).collect()),
    None => None
  };
  filters.by_damage_resist = match &params.damage_resist {
    Some(damage_resist) => Some(damage_resist.split(",").map(|s| s.to_string()).collect()),
    None => None
  };
  filters.by_conditions = match &params.conditions {
    Some(conditions) => Some(conditions.split(",").map(|s| s.to_string()).collect()),
    None => None
  };
  filters.by_saving_throw = match &params.saving_throw {
    Some(saving_throw) => Some(saving_throw.split(",").map(|s| s.to_string()).collect()),
    None => None
  };
  filters.by_attack_type = match &params.attack_type {
    Some(attack_type) => Some(attack_type.split(",").map(|s| s.to_string()).collect()),
    None => None
  };
  // Limit must be between 1 and 100
  let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(100), 1), 100);
  let total_count = QuerySpell::get_count(&filters).unwrap();
  let max_page = std::cmp::max((total_count as f64 / limit as f64).ceil() as i32, 1);
  // Page must be between 1 and max_page
  let page = std::cmp::min(std::cmp::max(params.page.unwrap_or(1), 1), max_page);

  match web::block(move || QuerySpell::get_all(&filters, limit, page)).await.unwrap() {
    Ok(spells) => {
      let mut response: Vec<Spell> = Vec::new();
      for query_spell in spells {
        let id = query_spell.id;
        let mut spell = Spell::from(query_spell);
        spell.id = Some(id);
        response.push(spell);
      }
      HttpResponse::Ok().json(Response {
        data: response,
        metadata: Some(Metadata {
          total: total_count as i32,
          limit,
          page,
          pages: max_page
        })
      })
    },
    Err(err) => {
      error!("{:?}", err.message);
      ResponseError::error_response(&err)
    }
  }
}

#[get("/spells/{id}")]
async fn get_by_id(id: web::Path<String>) -> HttpResponse {
  let id = match id.parse::<i32>() {
    Ok(id) => id,
    Err(err) => return ResponseError::error_response(&ServiceError {
      status: 422,
      message: err.to_string()
    })
  };
  match web::block(move || QuerySpell::get_by_id(id)).await.unwrap() {
    Ok(query_spell) => {
      let id = query_spell.id;
      let mut spell = Spell::from(query_spell);
      spell.id = Some(id);
      HttpResponse::Ok().json(Response {
        data: spell,
        metadata: None
      })
    },
    Err(err) => {
      error!("{:?}", err.message);
      ResponseError::error_response(&err)
    }
  }
}

#[post("/spells")]
async fn create(spell: web::Json<Spell>, auth: JwtAuth) -> HttpResponse {
  let _ = match verify_role(&auth, "admin") {
    Ok(_) => {},
    Err(err) => return ResponseError::error_response(&err)
  };
  match InsertSpell::insert(spell.into_inner().into()) {
    Ok(spell) => HttpResponse::Created().json(Spell::from(spell)),
    Err(err) => {
      error!("{:?}", err.message);
      ResponseError::error_response(&err)
    }
  }
}

#[put("/spells/{id}")]
async fn update(id: web::Path<String>, spell: web::Json<Spell>, auth: JwtAuth) -> HttpResponse {
  let _ = match verify_role(&auth, "admin") {
    Ok(_) => {},
    Err(err) => return ResponseError::error_response(&err)
  };
  let id = match id.parse::<i32>() {
    Ok(id) => id,
    Err(err) => return ResponseError::error_response(&ServiceError {
      status: 422,
      message: err.to_string()
    })
  };
  match web::block(move || InsertSpell::update(id, spell.into_inner().into())).await.unwrap() {
    Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
    Err(err) => {
      error!("{:?}", err.message);
      ResponseError::error_response(&err)
    }
  }
}

#[delete("/spells/{id}")]
async fn delete(id: web::Path<String>, auth: JwtAuth) -> HttpResponse {
  let _ = match verify_role(&auth, "admin") {
    Ok(_) => {},
    Err(err) => return ResponseError::error_response(&err)
  };
  let id = match id.parse::<i32>() {
    Ok(id) => id,
    Err(err) => return ResponseError::error_response(&ServiceError {
      status: 422,
      message: err.to_string()
    })
  };
  match web::block(move || QuerySpell::delete(id)).await.unwrap() {
    Ok(spell) => HttpResponse::Ok().json(Spell::from(spell)),
    Err(err) => {
      error!("{:?}", err.message);
      ResponseError::error_response(&err)
    }
  }
}

pub fn init_routes(config: &mut web::ServiceConfig) {
  config.service(web::scope("dnd")
    .service(get_all)
    .service(get_by_id)
    .service(create)
    .service(update)
  );
}