use rocket::{
    form::Form,
    get, post,
    response::{Flash, Redirect},
    routes, FromForm, Route, State,
};
use sqlx::SqlitePool;

use crate::model::{
    log::Log,
    planned_event::PlannedEvent,
    trip::{CoxHelpError, Trip, TripDeleteError, TripHelpDeleteError, TripUpdateError},
    tripdetails::{TripDetails, TripDetailsToAdd},
    user::CoxUser,
};

#[post("/trip", data = "<data>")]
async fn create(
    db: &State<SqlitePool>,
    data: Form<TripDetailsToAdd<'_>>,
    cox: CoxUser,
) -> Flash<Redirect> {
    let trip_details_id = TripDetails::create(db, data.into_inner()).await;
    let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just
                                                                                    //created
    Trip::new_own(db, &cox, trip_details).await; //TODO: fix

    //Log::create(
    //    db,
    //    format!(
    //        "Cox {} created trip on {} @ {} for {} rower",
    //        cox.name, trip_details.day, trip_details.planned_starting_time, trip_details.max_people,
    //    ),
    //)
    //.await;

    Flash::success(Redirect::to("/planned"), "Ausfahrt erfolgreich erstellt.")
}

#[derive(FromForm)]
struct EditTripForm<'r> {
    max_people: i32,
    notes: Option<&'r str>,
    trip_type: Option<i64>,
    always_show: bool,
    is_locked: bool,
}

#[post("/trip/<trip_id>", data = "<data>")]
async fn update(
    db: &State<SqlitePool>,
    data: Form<EditTripForm<'_>>,
    trip_id: i64,
    cox: CoxUser,
) -> Flash<Redirect> {
    if let Some(trip) = Trip::find_by_id(db, trip_id).await {
        match Trip::update_own(
            db,
            &cox,
            &trip,
            data.max_people,
            data.notes,
            data.trip_type,
            data.always_show,
            data.is_locked,
        )
        .await
        {
            Ok(_) => Flash::success(
                Redirect::to("/planned"),
                "Ausfahrt erfolgreich aktualisiert.",
            ),
            Err(TripUpdateError::NotYourTrip) => {
                Flash::error(Redirect::to("/planned"), "Nicht deine Ausfahrt!")
            }
            Err(TripUpdateError::TripDetailsDoesNotExist) => {
                Flash::error(Redirect::to("/planned"), "Ausfahrt gibt's nicht")
            }
        }
    } else {
        Flash::error(Redirect::to("/planned"), "Ausfahrt gibt's nicht")
    }
}

#[get("/join/<planned_event_id>")]
async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Flash<Redirect> {
    if let Some(planned_event) = PlannedEvent::find_by_id(db, planned_event_id).await {
        match Trip::new_join(db, &cox, &planned_event).await {
            Ok(_) => {
                Log::create(
                    db,
                    format!(
                        "Cox {} helps at planned_event.id={}",
                        cox.name, planned_event_id,
                    ),
                )
                .await;
                Flash::success(Redirect::to("/planned"), "Danke für's helfen!")
            }
            Err(CoxHelpError::AlreadyRegisteredAsCox) => {
                Flash::error(Redirect::to("/planned"), "Du hilfst bereits aus!")
            }
            Err(CoxHelpError::AlreadyRegisteredAsRower) => Flash::error(
                Redirect::to("/planned"),
                "Du hast dich bereits als Ruderer angemeldet!",
            ),
            Err(CoxHelpError::DetailsLocked) => {
                Flash::error(Redirect::to("/planned"), "Boot ist bereits eingeteilt.")
            }
        }
    } else {
        Flash::error(Redirect::to("/planned"), "Event gibt's nicht")
    }
}

#[get("/remove/trip/<trip_id>")]
async fn remove_trip(db: &State<SqlitePool>, trip_id: i64, cox: CoxUser) -> Flash<Redirect> {
    let trip = Trip::find_by_id(db, trip_id).await;
    match trip {
        None => Flash::error(Redirect::to("/planned"), "Trip gibt's nicht!"),
        Some(trip) => match trip.delete(db, &cox).await {
            Ok(_) => {
                Log::create(db, format!("Cox {} deleted trip.id={}", cox.name, trip_id)).await;
                Flash::success(Redirect::to("/planned"), "Erfolgreich gelöscht!")
            }
            Err(TripDeleteError::SomebodyAlreadyRegistered) => Flash::error(
                Redirect::to("/planned"),
                "Ausfahrt kann nicht gelöscht werden, da bereits jemand registriert ist!",
            ),
            Err(TripDeleteError::NotYourTrip) => {
                Flash::error(Redirect::to("/planned"), "Nicht deine Ausfahrt!")
            }
        },
    }
}

#[get("/remove/<planned_event_id>")]
async fn remove(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Flash<Redirect> {
    if let Some(planned_event) = PlannedEvent::find_by_id(db, planned_event_id).await {
        match Trip::delete_by_planned_event(db, &cox, &planned_event).await {
            Ok(_) => {
                Log::create(
                    db,
                    format!(
                        "Cox {} deleted registration for planned_event.id={}",
                        cox.name, planned_event_id
                    ),
                )
                .await;

                Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!")
            }
            Err(TripHelpDeleteError::DetailsLocked) => {
                Flash::error(Redirect::to("/planned"), "Boot bereits eingeteilt")
            }
            Err(TripHelpDeleteError::CoxNotHelping) => {
                Flash::error(Redirect::to("/planned"), "Steuermann hilft nicht aus...")
            }
        }
    } else {
        Flash::error(Redirect::to("/planned"), "Planned_event does not exist.")
    }
}

pub fn routes() -> Vec<Route> {
    routes![create, join, remove, remove_trip, update]
}

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

    use crate::{model::trip::Trip, testdb};

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

        assert_eq!(
            0,
            Trip::get_for_day(&db, NaiveDate::from_ymd_opt(2999, 12, 30).unwrap())
                .await
                .len()
        );

        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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client
            .post("/cox/trip")
            .header(ContentType::Form)
            .body("day=2999-12-30&planned_starting_time=12:34&max_people=42&allow_guests=false");
        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:successAusfahrt erfolgreich erstellt."
        );

        assert_eq!(
            1,
            Trip::get_for_day(&db, NaiveDate::from_ymd_opt(2999, 12, 30).unwrap())
                .await
                .len()
        );
    }

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

        let trip = &Trip::get_for_day(&db, NaiveDate::from_ymd_opt(1970, 01, 02).unwrap()).await[0];
        assert_eq!(1, trip.trip.max_people);
        assert_eq!(
            "trip_details for trip from cox",
            &trip.trip.notes.clone().unwrap()
        );

        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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client
            .post("/cox/trip/1")
            .header(ContentType::Form)
            .body("notes=my-new-notes&max_people=12");
        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:successAusfahrt erfolgreich aktualisiert."
        );

        let trip = &Trip::get_for_day(&db, NaiveDate::from_ymd_opt(1970, 01, 02).unwrap()).await[0];
        assert_eq!(12, trip.trip.max_people);
        assert_eq!("my-new-notes", &trip.trip.notes.clone().unwrap());
    }

    #[sqlx::test]
    fn test_trip_update_wrong_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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client
            .post("/cox/trip/9999")
            .header(ContentType::Form)
            .body("notes=my-new-notes&max_people=12");
        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(), "5:errorAusfahrt gibt's nicht");
    }

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

        let trip = &Trip::get_for_day(&db, NaiveDate::from_ymd_opt(1970, 01, 02).unwrap()).await[0];
        assert_eq!(1, trip.trip.max_people);
        assert_eq!(
            "trip_details for trip from cox",
            &trip.trip.notes.clone().unwrap()
        );

        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=cox2&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client
            .post("/cox/trip/1")
            .header(ContentType::Form)
            .body("notes=my-new-notes&max_people=12");
        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(), "5:errorNicht deine Ausfahrt!");
    }

    #[sqlx::test]
    fn test_trip_join() {
        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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client.get("/cox/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:successDanke für's helfen!");

        let req = client.get("/cox/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(), "5:errorDu hilfst bereits aus!");
    }

    #[sqlx::test]
    fn test_trip_join_already_rower() {
        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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

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

        let req = client.get("/cox/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(),
            "5:errorDu hast dich bereits als Ruderer angemeldet!"
        );
    }

    #[sqlx::test]
    fn test_trip_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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client.get("/cox/join/9999");
        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(), "5:errorEvent gibt's nicht");
    }

    #[sqlx::test]
    fn test_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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

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

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

        assert_eq!(flash_cookie.value(), "7:successDanke für's helfen!");

        let req = client.get("/cox/join/1");
        let _ = req.dispatch().await;

        let req = client.get("/cox/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_remove_wrong_id() {
        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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client.get("/cox/remove/999");
        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(), "5:errorPlanned_event does not exist.");
    }

    #[sqlx::test]
    fn test_remove_cox_not_participating() {
        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=cox&password=cox"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client.get("/cox/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(), "5:errorSteuermann hilft nicht aus...");
    }
}
