use rocket::{
    get,
    request::FlashMessage,
    response::{Flash, Redirect},
    routes, Route, State,
};
use rocket_dyn_templates::Template;
use sqlx::SqlitePool;
use tera::Context;

use crate::model::{
    log::Log,
    logbook::Logbook,
    tripdetails::TripDetails,
    triptype::TripType,
    user::{AllowedForPlannedTripsUser, User, UserWithRoles},
    usertrip::{UserTrip, UserTripDeleteError, UserTripError},
};

#[get("/")]
async fn index(
    db: &State<SqlitePool>,
    user: AllowedForPlannedTripsUser,
    flash: Option<FlashMessage<'_>>,
) -> Template {
    let user: User = user.into();

    let mut context = Context::new();

    if user.has_role(db, "cox").await || user.has_role(db, "planned_event").await {
        let triptypes = TripType::all(db).await;
        context.insert("trip_types", &triptypes);
    }

    if user.has_role(db, "scheckbuch").await {
        let last_trips = Logbook::completed_with_user(db, &user).await;
        context.insert("last_trips", &last_trips);
    }

    let days = user.get_days(db).await;

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

    context.insert("fee", &user.fee(db).await);
    context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await);
    context.insert("days", &days);
    Template::render("planned", context.into_json())
}

#[get("/join/<trip_details_id>?<user_note>")]
async fn join(
    db: &State<SqlitePool>,
    trip_details_id: i64,
    user: AllowedForPlannedTripsUser,
    user_note: Option<String>,
) -> Flash<Redirect> {
    let user: User = user.into();

    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
        return Flash::error(Redirect::to("/"), "Trip_details do not exist.");
    };

    match UserTrip::create(db, &user, &trip_details, user_note).await {
        Ok(registered_user) => {
            if registered_user == user.name {
                Log::create(
                    db,
                    format!(
                        "User {} registered for trip_details.id={}",
                        user.name, trip_details_id
                    ),
                )
                .await;
            }else{
                Log::create(
                    db,
                    format!(
                        "User {} registered the guest '{}' for trip_details.id={}",
                        user.name, registered_user, trip_details_id
                    ),
                ).await;
            }
            Flash::success(Redirect::to("/planned"), "Erfolgreich angemeldet!")
        }
        Err(UserTripError::EventAlreadyFull) => {
            Flash::error(Redirect::to("/planned"), "Event bereits ausgebucht!")
        }
        Err(UserTripError::AlreadyRegistered) => {
            Flash::error(Redirect::to("/planned"), "Du nimmst bereits teil!")
        }
        Err(UserTripError::AlreadyRegisteredAsCox) => {
            Flash::error(Redirect::to("/planned"), "Du hilfst bereits als Steuerperson aus!")
        }
        Err(UserTripError::CantRegisterAtOwnEvent) => Flash::error(
            Redirect::to("/planned"),
            "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)",
        ),
        Err(UserTripError::GuestNotAllowedForThisEvent) => Flash::error(
            Redirect::to("/planned"),
            "Bei dieser Ausfahrt können leider keine Gäste mitfahren.",
        ),
        Err(UserTripError::NotAllowedToAddGuest) => Flash::error(
            Redirect::to("/planned"),
            "Du darfst keine Gäste hinzufügen.",
        ),
        Err(UserTripError::DetailsLocked) => Flash::error(
            Redirect::to("/planned"),
            "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.",
        ),
    }
}

#[get("/remove/<trip_details_id>/<name>")]
async fn remove_guest(
    db: &State<SqlitePool>,
    trip_details_id: i64,
    user: AllowedForPlannedTripsUser,
    name: String,
) -> Flash<Redirect> {
    let user: User = user.into();

    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
        return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist");
    };

    match UserTrip::delete(db, &user, &trip_details, Some(name)).await {
        Ok(_) => {
            Log::create(
                db,
                format!(
                    "User {} unregistered for trip_details.id={}",
                    user.name, trip_details_id
                ),
            )
            .await;

            Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!")
        }
        Err(UserTripDeleteError::DetailsLocked) => {
            Log::create(
                db,
                format!(
                    "User {} tried to unregister for locked trip_details.id={}",
                    user.name, trip_details_id
                ),
            )
            .await;

            Flash::error(Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
        }
        Err(UserTripDeleteError::GuestNotParticipating) => {
            Flash::error(Redirect::to("/planned"), "Gast nicht angemeldet.")
        }
        Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error(
            Redirect::to("/planned"),
            "Keine Berechtigung um den Gast zu entfernen.",
        ),
    }
}

#[get("/remove/<trip_details_id>")]
async fn remove(
    db: &State<SqlitePool>,
    trip_details_id: i64,
    user: AllowedForPlannedTripsUser,
) -> Flash<Redirect> {
    let user: User = user.into();

    let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else {
        return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist");
    };

    match UserTrip::delete(db, &user, &trip_details, None).await {
        Ok(_) => {
            Log::create(
                db,
                format!(
                    "User {} unregistered for trip_details.id={}",
                    user.name, trip_details_id
                ),
            )
            .await;

            Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!")
        }
        Err(UserTripDeleteError::DetailsLocked) => {
            Log::create(
                db,
                format!(
                    "User {} tried to unregister for locked trip_details.id={}",
                    user.name, trip_details_id
                ),
            )
            .await;

            Flash::error(Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
        }
        Err(_) => {
            panic!("Not possible to be here");
        }
    }
}

pub fn routes() -> Vec<Route> {
    routes![index, join, remove, remove_guest]
}

#[cfg(test)]
mod test {
    use rocket::{
        http::{ContentType, Status},
        local::asynchronous::Client,
    };
    use sqlx::SqlitePool;

    use crate::testdb;

    #[sqlx::test]
    fn test_join_and_remove() {
        let db = testdb!();

        let rocket = rocket::build().manage(db.clone());
        let rocket = crate::tera::config(rocket);

        let client = Client::tracked(rocket).await.unwrap();
        let login = client
            .post("/auth")
            .header(ContentType::Form) // Set the content type to form
            .body("name=rower&password=rower"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client.get("/planned/join/1");
        let response = req.dispatch().await;

        assert_eq!(response.status(), Status::SeeOther);
        assert_eq!(response.headers().get("Location").next(), Some("/planned"));

        let flash_cookie = response
            .cookies()
            .get("_flash")
            .expect("Expected flash cookie");

        assert_eq!(flash_cookie.value(), "7:successErfolgreich angemeldet!");

        let req = client.get("/planned/remove/1");
        let response = req.dispatch().await;

        assert_eq!(response.status(), Status::SeeOther);
        assert_eq!(response.headers().get("Location").next(), Some("/planned"));

        let flash_cookie = response
            .cookies()
            .get("_flash")
            .expect("Expected flash cookie");

        assert_eq!(flash_cookie.value(), "7:successErfolgreich abgemeldet!");
    }

    #[sqlx::test]
    fn test_join_invalid_event() {
        let db = testdb!();

        let rocket = rocket::build().manage(db.clone());
        let rocket = crate::tera::config(rocket);

        let client = Client::tracked(rocket).await.unwrap();
        let login = client
            .post("/auth")
            .header(ContentType::Form) // Set the content type to form
            .body("name=rower&password=rower"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client.get("/planned/join/9999");
        let response = req.dispatch().await;

        assert_eq!(response.status(), Status::SeeOther);
        assert_eq!(response.headers().get("Location").next(), Some("/"));

        let flash_cookie = response
            .cookies()
            .get("_flash")
            .expect("Expected flash cookie");

        assert_eq!(flash_cookie.value(), "5:errorTrip_details do not exist.");
    }
}
