use chrono::NaiveDate;
use rocket::{
    form::Form,
    get, post,
    request::FlashMessage,
    response::{Flash, Redirect},
    routes, FromForm, Route, State,
};
use rocket_dyn_templates::Template;
use sqlx::SqlitePool;
use tera::Context;

use crate::{
    model::{
        boat::Boat,
        boatreservation::{BoatReservation, BoatReservationToAdd},
        log::Log,
        user::{DonauLinzUser, User, UserWithRolesAndNotificationCount},
    },
    tera::log::KioskCookie,
};

#[get("/")]
async fn index_kiosk(
    db: &State<SqlitePool>,
    flash: Option<FlashMessage<'_>>,
    _kiosk: KioskCookie,
) -> Template {
    let boatreservations = BoatReservation::all_future(db).await;

    let mut context = Context::new();
    if let Some(msg) = flash {
        context.insert("flash", &msg.into_inner());
    }

    let linz_boats = Boat::all_for_boatshouse(db).await;
    let mut boats = Vec::new();
    for boat in linz_boats {
        if boat.boat.owner.is_none() {
            boats.push(boat);
        }
    }

    context.insert("boatreservations", &boatreservations);
    context.insert("boats", &boats);
    context.insert("user", &User::all(db).await);
    context.insert("show_kiosk_header", &true);

    Template::render("boatreservations", context.into_json())
}

#[get("/", rank = 2)]
async fn index(
    db: &State<SqlitePool>,
    flash: Option<FlashMessage<'_>>,
    user: DonauLinzUser,
) -> Template {
    let boatreservations = BoatReservation::all_future(db).await;

    let mut context = Context::new();
    if let Some(msg) = flash {
        context.insert("flash", &msg.into_inner());
    }

    let linz_boats = Boat::all_for_boatshouse(db).await;
    let mut boats = Vec::new();
    for boat in linz_boats {
        if boat.boat.owner.is_none() {
            boats.push(boat);
        }
    }

    context.insert("boatreservations", &boatreservations);
    context.insert("boats", &boats);
    context.insert("user", &User::all(db).await);
    context.insert(
        "loggedin_user",
        &UserWithRolesAndNotificationCount::from_user(user.into(), db).await,
    );

    Template::render("boatreservations", context.into_json())
}

#[derive(Debug, FromForm)]
pub struct FormBoatReservationToAdd<'r> {
    pub boat_id: i64,
    pub start_date: &'r str,
    pub end_date: &'r str,
    pub time_desc: &'r str,
    pub usage: &'r str,
    pub user_id_applicant: Option<i64>,
}

#[post("/new", data = "<data>", rank = 2)]
async fn create<'r>(
    db: &State<SqlitePool>,
    data: Form<FormBoatReservationToAdd<'r>>,
    user: DonauLinzUser,
) -> Flash<Redirect> {
    let user_applicant: User = user.into();
    let boat = Boat::find_by_id(db, data.boat_id as i32).await.unwrap();
    let boatreservation_to_add = BoatReservationToAdd {
        boat: &boat,
        start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(),
        end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(),
        time_desc: data.time_desc,
        usage: data.usage,
        user_applicant: &user_applicant,
    };
    match BoatReservation::create(db, boatreservation_to_add).await {
        Ok(_) => Flash::success(
            Redirect::to("/boatreservation"),
            "Reservierung erfolgreich hinzugefügt",
        ),
        Err(e) => Flash::error(Redirect::to("/boatreservation"), format!("Fehler: {e}")),
    }
}

#[post("/new", data = "<data>")]
async fn create_from_kiosk<'r>(
    db: &State<SqlitePool>,
    data: Form<FormBoatReservationToAdd<'r>>,
    _kiosk: KioskCookie,
) -> Flash<Redirect> {
    let user_applicant: User = User::find_by_id(db, data.user_id_applicant.unwrap() as i32)
        .await
        .unwrap();
    let boat = Boat::find_by_id(db, data.boat_id as i32).await.unwrap();
    let boatreservation_to_add = BoatReservationToAdd {
        boat: &boat,
        start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(),
        end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(),
        time_desc: data.time_desc,
        usage: data.usage,
        user_applicant: &user_applicant,
    };
    match BoatReservation::create(db, boatreservation_to_add).await {
        Ok(_) => Flash::success(
            Redirect::to("/boatreservation"),
            "Reservierung erfolgreich hinzugefügt",
        ),
        Err(e) => Flash::error(Redirect::to("/boatreservation"), format!("Fehler: {e}")),
    }
}

#[derive(FromForm, Debug)]
pub struct ReservationEditForm {
    pub(crate) id: i32,
    pub(crate) time_desc: String,
    pub(crate) usage: String,
}

#[post("/", data = "<data>")]
async fn update(
    db: &State<SqlitePool>,
    data: Form<ReservationEditForm>,
    user: User,
) -> Flash<Redirect> {
    let Some(reservation) = BoatReservation::find_by_id(db, data.id).await else {
        return Flash::error(
            Redirect::to("/boatreservation"),
            format!("Reservation with ID {} does not exist!", data.id),
        );
    };

    if user.id != reservation.user_id_applicant && !user.has_role(db, "admin").await {
        return Flash::error(
            Redirect::to("/boatreservation"),
            "Not allowed to update reservation (only admins + creator do so).".to_string(),
        );
    }

    Log::create(
        db,
        format!(
            "{} updated reservation from {reservation:?} to {data:?}",
            user.name
        ),
    )
    .await;

    reservation.update(db, data.into_inner()).await;

    Flash::success(
        Redirect::to("/boatreservation"),
        "Reservierung erfolgreich bearbeitet",
    )
}

#[get("/<reservation_id>/delete")]
async fn delete<'r>(
    db: &State<SqlitePool>,
    reservation_id: i32,
    user: DonauLinzUser,
) -> Flash<Redirect> {
    let reservation = BoatReservation::find_by_id(db, reservation_id)
        .await
        .unwrap();

    if user.id == reservation.user_id_applicant || user.has_role(db, "admin").await {
        reservation.delete(db).await;
        Flash::success(
            Redirect::to("/boatreservation"),
            "Reservierung erfolgreich gelöscht",
        )
    } else {
        Flash::error(
            Redirect::to("/boatreservation"),
            "Nur der Reservierer darf die Reservierung löschen.".to_string(),
        )
    }
}

pub fn routes() -> Vec<Route> {
    routes![
        index,
        index_kiosk,
        create,
        create_from_kiosk,
        delete,
        update
    ]
}