use crate::controllers::HttpResult;
use crate::extractors::accommodation_extractor::FamilyAndAccommodationInPath;
use crate::extractors::accommodation_reservation_extractor::FamilyAndAccommodationReservationInPath;
use crate::extractors::family_extractor::FamilyInPath;
use crate::models::{Accommodation, AccommodationReservationID, NewAccommodationReservation};
use crate::services::accommodations_reservations_service;
use crate::utils::time_utils::time;
use actix_web::{HttpResponse, web};

#[derive(serde::Deserialize)]
pub struct UpdateReservationQuery {
    start: usize,
    end: usize,
}

impl UpdateReservationQuery {
    /// Check whether a reservation request is valid or not
    async fn validate(
        &self,
        a: &Accommodation,
        resa_id: Option<AccommodationReservationID>,
    ) -> anyhow::Result<Option<&str>> {
        if !a.open_to_reservations {
            return Ok(Some(
                "The accommodation is not open to reservations creation / update!",
            ));
        }

        if (self.start as i64) < (time() as i64 - 3600 * 24 * 30) {
            return Ok(Some("Start time is too far in the past!"));
        }

        if self.start == self.end {
            return Ok(Some("Start and end time must be different!"));
        }

        if self.start > self.end {
            return Ok(Some("End time happens before start time!"));
        }

        let existing = accommodations_reservations_service::get_reservations_for_time_interval(
            a.id(),
            self.start,
            self.end,
        )
        .await?;

        if existing
            .iter()
            .any(|r| r.validated != Some(false) && resa_id != Some(r.id()))
        {
            return Ok(Some("This reservation is in conflict with another one!"));
        }

        Ok(None)
    }
}

/// Create a reservation
pub async fn create_reservation(
    a: FamilyAndAccommodationInPath,
    req: web::Json<UpdateReservationQuery>,
) -> HttpResult {
    if let Some(err) = req.validate(&a, None).await? {
        return Ok(HttpResponse::BadRequest().json(err));
    }

    let mut reservation =
        accommodations_reservations_service::create(&NewAccommodationReservation {
            family_id: a.family_id().0,
            accommodation_id: a.id().0,
            user_id: a.membership().user_id().0,
            time_create: time() as i64,
            time_update: time() as i64,
            reservation_start: req.start as i64,
            reservation_end: req.end as i64,
        })
        .await?;

    // Auto validate reservation if requested
    if !a.need_validation {
        reservation.validated = Some(true);

        accommodations_reservations_service::update(&mut reservation).await?;
    }

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

/// Get the reservations for a given accommodation
pub async fn get_accommodation_reservations(a: FamilyAndAccommodationInPath) -> HttpResult {
    Ok(HttpResponse::Ok()
        .json(accommodations_reservations_service::get_all_of_accommodation(a.id()).await?))
}

#[derive(serde::Deserialize)]
pub struct CheckAvailabilityQuery {
    start: usize,
    end: usize,
}

/// Check reservation availability
pub async fn get_accommodation_reservations_for_interval(
    a: FamilyAndAccommodationInPath,
    req: web::Query<CheckAvailabilityQuery>,
) -> HttpResult {
    if req.start > req.end {
        return Ok(HttpResponse::BadRequest().json("start should be smaller than end!"));
    }

    let res = accommodations_reservations_service::get_reservations_for_time_interval(
        a.id(),
        req.start,
        req.end,
    )
    .await?;

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

/// Get the full list of accommodations reservations for a family
pub async fn full_list(m: FamilyInPath) -> HttpResult {
    Ok(HttpResponse::Ok()
        .json(accommodations_reservations_service::get_all_of_family(m.family_id()).await?))
}

/// Get a single accommodation reservation
pub async fn get_single(m: FamilyAndAccommodationReservationInPath) -> HttpResult {
    Ok(HttpResponse::Ok().json(m.to_reservation()))
}

/// Update a reservation
pub async fn update_single(
    m: FamilyAndAccommodationReservationInPath,
    req: web::Json<UpdateReservationQuery>,
) -> HttpResult {
    if let Some(err) = req.validate(m.as_accommodation(), Some(m.id())).await? {
        return Ok(HttpResponse::BadRequest().json(err));
    }

    if m.membership().user_id() != m.user_id() {
        return Ok(
            HttpResponse::BadRequest().json("Only the owner of a reservation can change it!")
        );
    }

    let need_validation = m.as_accommodation().need_validation;

    let mut reservation = m.to_reservation();
    reservation.reservation_start = req.start as i64;
    reservation.reservation_end = req.end as i64;

    if need_validation {
        reservation.validated = None;
    }

    accommodations_reservations_service::update(&mut reservation).await?;

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

/// Delete a reservation
pub async fn delete(m: FamilyAndAccommodationReservationInPath) -> HttpResult {
    if m.membership().user_id() != m.user_id() {
        return Ok(
            HttpResponse::BadRequest().json("Only the owner of a reservation can delete it!")
        );
    }

    accommodations_reservations_service::delete(m.to_reservation()).await?;

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

#[derive(serde::Deserialize)]
pub struct ValidateQuery {
    validate: bool,
}

/// Validate or reject a reservation
pub async fn validate_or_reject(
    m: FamilyAndAccommodationReservationInPath,
    q: web::Json<ValidateQuery>,
) -> HttpResult {
    if !m.membership().is_admin {
        return Ok(
            HttpResponse::BadRequest().json("Only a family admin can validate a reservation!")
        );
    }

    if m.validated == Some(q.validate) {
        return Ok(
            HttpResponse::AlreadyReported().json("This reservation has already been processed!")
        );
    }

    // In case of re-validation, check that the time is still available
    if m.validated == Some(false) && q.validate {
        let potential_conflicts =
            accommodations_reservations_service::get_reservations_for_time_interval(
                m.accommodation_id(),
                m.reservation_start as usize,
                m.reservation_end as usize,
            )
            .await?;

        if potential_conflicts
            .iter()
            .any(|a| a.validated != Some(false))
        {
            return Ok(HttpResponse::Conflict().json(
                "This cannot be accepted as it would create a conflict with another reservation!",
            ));
        }
    }

    // Update reservation validation status
    let mut reservation = m.to_reservation();
    reservation.validated = Some(q.validate);
    accommodations_reservations_service::update(&mut reservation).await?;

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