use ical::{generator::*, *};

use actix_web::{HttpResponse, web};
use chrono::DateTime;

use crate::constants::StaticConstraints;
use crate::controllers::HttpResult;
use crate::extractors::accommodation_reservation_calendar_extractor::FamilyAndAccommodationReservationCalendarInPath;
use crate::extractors::family_extractor::FamilyInPath;
use crate::models::{AccommodationID, ReservationStatus};
use crate::services::{
    accommodations_list_service, accommodations_reservations_calendars_service,
    accommodations_reservations_service, families_service,
};
use crate::utils::time_utils::time;

#[derive(serde::Deserialize)]
pub struct CreateCalendarQuery {
    accommodation_id: Option<AccommodationID>,
    name: String,
}

/// Create a calendar
pub async fn create(a: FamilyInPath, req: web::Json<CreateCalendarQuery>) -> HttpResult {
    let accommodation_id = match req.accommodation_id {
        Some(i) => {
            let accommodation = match accommodations_list_service::get_by_id(i).await {
                Ok(a) => a,
                Err(e) => {
                    log::error!("Failed to get accommodation information! {e}");
                    return Ok(HttpResponse::NotFound()
                        .json("The accommodation was not found in the family!"));
                }
            };

            if accommodation.family_id() != a.family_id() {
                return Ok(
                    HttpResponse::NotFound().json("The accommodation was not found in the family!")
                );
            }

            Some(accommodation.id())
        }
        None => None,
    };

    let conf = StaticConstraints::default();
    if !conf.accommodation_calendar_name_len.validate(&req.name) {
        return Ok(HttpResponse::BadRequest().json("Invalid accommodation name!"));
    }

    let calendar = accommodations_reservations_calendars_service::create(
        a.user_id(),
        a.family_id(),
        accommodation_id,
        &req.name,
    )
    .await?;

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

/// Get the list of calendars of a user
pub async fn get_list(a: FamilyInPath) -> HttpResult {
    let users =
        accommodations_reservations_calendars_service::get_all_of_user(a.user_id(), a.family_id())
            .await?;
    Ok(HttpResponse::Ok().json(users))
}

/// Delete a calendar
pub async fn delete(resa: FamilyAndAccommodationReservationCalendarInPath) -> HttpResult {
    accommodations_reservations_calendars_service::delete(resa.to_reservation()).await?;
    Ok(HttpResponse::Ok().json("Calendar successfully deleted"))
}

fn fmt_date(time: i64) -> String {
    let res = DateTime::from_timestamp(time, 0).expect("Failed to parse date");

    /*format!(
        "{:0>4}{:0>2}{:0>2}T{:0>2}{:0>2}",
        res.year(),
        res.month(),
        res.day(),
        res.minute(),
        res.second()
    )*/

    res.format("%Y%m%dT%H%M%S").to_string()
}

#[derive(serde::Deserialize)]
pub struct AnonymousAccessURL {
    token: String,
}

/// Get the content of the calendar
pub async fn anonymous_access(req: web::Path<AnonymousAccessURL>) -> HttpResult {
    let mut calendar =
        match accommodations_reservations_calendars_service::get_by_token(&req.token).await {
            Ok(c) => c,
            Err(e) => {
                log::error!("Calendar information could not be retrieved: {e}");
                return Ok(HttpResponse::NotFound().body("Calendar not found!"));
            }
        };

    let accommodations =
        accommodations_list_service::get_all_of_family(calendar.family_id()).await?;
    let members = families_service::get_memberships_of_family(calendar.family_id()).await?;

    // Get calendar associated events
    let events = match calendar.accommodation_id() {
        None => {
            accommodations_reservations_service::get_all_of_family(calendar.family_id()).await?
        }
        Some(a) => accommodations_reservations_service::get_all_of_accommodation(a).await?,
    };

    let mut cal = IcalCalendarBuilder::version("2.0")
        .gregorian()
        .prodid("-//geneit//")
        .build();

    for ev in events {
        let accommodation = accommodations
            .iter()
            .find(|a| a.id() == ev.accommodation_id())
            .unwrap();
        let member_name = members
            .iter()
            .find(|a| a.membership.user_id() == ev.user_id())
            .map(|m| m.user_name.as_str())
            .unwrap_or("other user");

        let event = IcalEventBuilder::tzid("Europe/Paris")
            .uid(format!("resa-{}", ev.id().0))
            .changed(fmt_date(ev.time_update))
            .start(fmt_date(ev.reservation_start))
            .end(fmt_date(ev.reservation_end))
            .set(ical_property!("SUMMARY", member_name))
            .set(ical_property!("LOCATION", &accommodation.name))
            .set(ical_property!(
                "STATUS",
                match ev.status() {
                    ReservationStatus::Pending => "TENTATIVE",
                    ReservationStatus::Accepted => "CONFIRMED",
                    ReservationStatus::Rejected => "CANCELLED",
                }
            ))
            .build();
        cal.events.push(event);
    }

    if calendar.should_update_last_used() {
        calendar.time_used = time() as i64;
        accommodations_reservations_calendars_service::update(&calendar).await?;
    }

    Ok(HttpResponse::Ok()
        .content_type("text/calendar")
        .body(cal.generate()))
}
