use crate::constants::StaticConstraints;
use crate::controllers::HttpResult;
use crate::extractors::accommodation_extractor::FamilyAndAccommodationInPath;
use crate::extractors::family_extractor::{FamilyInPath, FamilyInPathWithAdminMembership};
use crate::models::Accommodation;
use crate::services::accommodations_list_service;
use actix_web::{web, HttpResponse};

#[derive(thiserror::Error, Debug)]
enum AccommodationListControllerErr {
    #[error("Invalid name length!")]
    InvalidNameLength,
    #[error("Invalid description length!")]
    InvalidDescriptionLength,
    #[error("Malformed color!")]
    MalformedColor,
}

#[derive(serde::Deserialize, Clone)]
pub struct AccommodationRequest {
    pub name: String,
    pub need_validation: bool,
    pub description: Option<String>,
    pub color: Option<String>,
    pub open_to_reservations: bool,
}

impl AccommodationRequest {
    pub async fn to_accommodation(self, accommodation: &mut Accommodation) -> anyhow::Result<()> {
        let c = StaticConstraints::default();

        if !c.accommodation_name_len.validate(&self.name) {
            return Err(AccommodationListControllerErr::InvalidNameLength.into());
        }
        accommodation.name = self.name;

        if let Some(d) = &self.description {
            if !c.accommodation_description_len.validate(d) {
                return Err(AccommodationListControllerErr::InvalidDescriptionLength.into());
            }
        }
        accommodation.description.clone_from(&self.description);

        if let Some(c) = &self.color {
            if !lazy_regex::regex!("[a-fA-F0-9]{6}").is_match(c) {
                return Err(AccommodationListControllerErr::MalformedColor.into());
            }
        }
        accommodation.color.clone_from(&self.color);

        accommodation.need_validation = self.need_validation;
        accommodation.open_to_reservations = self.open_to_reservations;
        Ok(())
    }
}

/// Create a new accommodation
pub async fn create(
    m: FamilyInPathWithAdminMembership,
    req: web::Json<AccommodationRequest>,
) -> HttpResult {
    let mut accommodation = accommodations_list_service::create(m.family_id()).await?;

    if let Err(e) = req.0.to_accommodation(&mut accommodation).await {
        log::error!("Failed to apply accommodation information! {e}");
        accommodations_list_service::delete(&mut accommodation).await?;
        return Ok(HttpResponse::BadRequest().body(e.to_string()));
    }

    if let Err(e) = accommodations_list_service::update(&mut accommodation).await {
        log::error!("Failed to update accommodation information! {e}");
        accommodations_list_service::delete(&mut accommodation).await?;
        return Ok(HttpResponse::InternalServerError().finish());
    }

    Ok(HttpResponse::Ok().json(accommodation))
}

/// Get the full list of accommodations
pub async fn get_full_list(m: FamilyInPath) -> HttpResult {
    Ok(HttpResponse::Ok()
        .json(accommodations_list_service::get_all_of_family(m.family_id()).await?))
}

/// Get the information of a single accommodation
pub async fn get_single(m: FamilyAndAccommodationInPath) -> HttpResult {
    Ok(HttpResponse::Ok().json(&m.to_accommodation()))
}

/// Update an accommodation
pub async fn update(
    m: FamilyAndAccommodationInPath,
    req: web::Json<AccommodationRequest>,
    _admin: FamilyInPathWithAdminMembership,
) -> HttpResult {
    let mut accommodation = m.to_accommodation();

    if let Err(e) = req.0.to_accommodation(&mut accommodation).await {
        log::error!("Failed to parse accommodation information! {e}");
        return Ok(HttpResponse::BadRequest().body(e.to_string()));
    }

    accommodations_list_service::update(&mut accommodation).await?;

    Ok(HttpResponse::Accepted().finish())
}

/// Delete an accommodation
pub async fn delete(
    m: FamilyAndAccommodationInPath,
    _admin: FamilyInPathWithAdminMembership,
) -> HttpResult {
    accommodations_list_service::delete(&mut m.to_accommodation()).await?;
    Ok(HttpResponse::Ok().finish())
}