use std::net::IpAddr;

use rocket::{
    form::Form,
    get,
    http::{Cookie, CookieJar},
    post,
    request::{self, FlashMessage, FromRequest},
    response::{Flash, Redirect},
    routes,
    time::{Duration, OffsetDateTime},
    Request, Route, State,
};
use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool;
use tera::Context;

use crate::model::{
    boat::Boat,
    boatreservation::BoatReservation,
    log::Log,
    logbook::{
        LogToAdd, LogToFinalize, Logbook, LogbookCreateError, LogbookDeleteError,
        LogbookUpdateError,
    },
    logtype::LogType,
    user::{AdminUser, DonauLinzUser, User, UserWithRoles, UserWithWaterStatus},
};

pub struct KioskCookie(String);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for KioskCookie {
    type Error = std::convert::Infallible;

    async fn from_request(request: &'r Request<'_>) -> request::Outcome<KioskCookie, Self::Error> {
        match request.cookies().get_private("kiosk") {
            Some(cookie) => request::Outcome::Success(KioskCookie(cookie.value().to_string())),
            None => request::Outcome::Forward(rocket::http::Status::SeeOther),
        }
    }
}

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

    let mut coxes: Vec<UserWithWaterStatus> = futures::future::join_all(
        User::cox(db)
            .await
            .into_iter()
            .map(|user| UserWithWaterStatus::from_user(user, db)),
    )
    .await;
    coxes.retain(|u| u.roles.contains(&"Donau Linz".into()));

    let mut users: Vec<UserWithWaterStatus> = futures::future::join_all(
        User::all(db)
            .await
            .into_iter()
            .map(|user| UserWithWaterStatus::from_user(user, db)),
    )
    .await;
    users.retain(|u| u.roles.contains(&"Donau Linz".into()));

    let logtypes = LogType::all(db).await;
    let distances = Logbook::distances(db).await;

    let on_water = Logbook::on_water(db).await;

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

    context.insert("boats", &boats);
    context.insert("reservations", &BoatReservation::all_future(db).await);
    context.insert("coxes", &coxes);
    context.insert("users", &users);
    context.insert("logtypes", &logtypes);
    context.insert(
        "loggedin_user",
        &UserWithRoles::from_user(user.into(), db).await,
    );
    context.insert("on_water", &on_water);
    context.insert("distances", &distances);

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

#[get("/show", rank = 3)]
async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template {
    let logs = Logbook::completed(db).await;

    Template::render(
        "log.completed",
        context!(logs, loggedin_user: &UserWithRoles::from_user(user.into(), db).await),
    )
}

#[get("/show?<year>", rank = 2)]
async fn show_for_year(db: &State<SqlitePool>, user: AdminUser, year: i32) -> Template {
    let logs = Logbook::completed_in_year(db, year).await;

    Template::render(
        "log.completed",
        context!(logs, loggedin_user: &UserWithRoles::from_user(user.user, db).await),
    )
}

#[get("/show")]
async fn show_kiosk(db: &State<SqlitePool>, _kiosk: KioskCookie) -> Template {
    let logs = Logbook::completed(db).await;

    Template::render("log.completed", context!(logs, show_kiosk_header: true))
}

#[get("/kiosk/ekrv2019/<loc>")]
async fn new_kiosk(
    db: &State<SqlitePool>,
    cookies: &CookieJar<'_>,
    loc: String,
    ip: Option<IpAddr>,
) -> Redirect {
    Log::create(
        db,
        format!("New kiosk cookie set for loc '{loc}' (IP={ip:?})"),
    )
    .await;
    let mut cookie = Cookie::new("kiosk", loc);
    cookie.set_expires(OffsetDateTime::now_utc() + Duration::weeks(12));
    cookies.add_private(cookie);
    Redirect::to("/log")
}

#[get("/")]
async fn kiosk(
    db: &State<SqlitePool>,
    flash: Option<FlashMessage<'_>>,
    _kiosk: KioskCookie,
) -> Template {
    let boats = Boat::all(db).await;
    let coxes: Vec<UserWithWaterStatus> = futures::future::join_all(
        User::cox(db)
            .await
            .into_iter()
            .map(|user| UserWithWaterStatus::from_user(user, db)),
    )
    .await;
    let users: Vec<UserWithWaterStatus> = futures::future::join_all(
        User::all(db)
            .await
            .into_iter()
            .map(|user| UserWithWaterStatus::from_user(user, db)),
    )
    .await;
    let logtypes = LogType::all(db).await;
    let distances = Logbook::distances(db).await;

    let on_water = Logbook::on_water(db).await;

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

    context.insert("boats", &boats);
    context.insert("reservations", &BoatReservation::all_future(db).await);
    context.insert("coxes", &coxes);
    context.insert("users", &users);
    context.insert("logtypes", &logtypes);
    context.insert("on_water", &on_water);
    context.insert("distances", &distances);
    context.insert("show_kiosk_header", &true);

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

async fn create_logbook(
    db: &SqlitePool,
    data: Form<LogToAdd>,
    user: &DonauLinzUser,
) -> Flash<Redirect> {
    match Logbook::create(
        db,
        data.into_inner(),
        user
    )
    .await
    {
        Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt erfolgreich hinzugefügt"),
        Err(LogbookCreateError::BoatAlreadyOnWater) => Flash::error(Redirect::to("/log"), "Boot schon am Wasser"),
        Err(LogbookCreateError::RowerAlreadyOnWater(rower)) => Flash::error(Redirect::to("/log"), format!("Ruderer {} schon am Wasser", rower.name)),
        Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"),"Boot gesperrt"),
        Err(LogbookCreateError::BoatNotFound) => Flash::error(Redirect::to("/log"), "Boot gibt's ned"),
        Err(LogbookCreateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")),
        Err(LogbookCreateError::RowerCreateError(rower, e)) => Flash::error(Redirect::to("/log"), format!("Fehler bei Ruderer {rower}: {e}")),
        Err(LogbookCreateError::ArrivalNotAfterDeparture) => Flash::error(Redirect::to("/log"), "Ankunftszeit kann nicht vor der Abfahrtszeit sein"),
        Err(LogbookCreateError::UserNotAllowedToUseBoat) => Flash::error(Redirect::to("/log"), "Schiffsführer darf dieses Boot nicht verwenden"),
        Err(LogbookCreateError::SteeringPersonNotInRowers) => Flash::error(Redirect::to("/log"), "Steuerperson nicht in Liste der Ruderer!"),
        Err(LogbookCreateError::ShipmasterNotInRowers) => Flash::error(Redirect::to("/log"), "Schiffsführer nicht in Liste der Ruderer!"),
        Err(LogbookCreateError::NotYourEntry) => Flash::error(Redirect::to("/log"), "Nicht deine Ausfahrt!"),
        Err(LogbookCreateError::ArrivalSetButNotRemainingTwo) => Flash::error(Redirect::to("/log"), "Ankunftszeit gesetzt aber nicht Distanz + Strecke"),
        Err(LogbookCreateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die in der letzten Woche enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten Philipp (Tel. nr. siehe Signal oder it@rudernlinz.at)."),

    }
}

#[post("/", data = "<data>", rank = 2)]
async fn create(
    db: &State<SqlitePool>,
    data: Form<LogToAdd>,
    user: DonauLinzUser,
) -> Flash<Redirect> {
    Log::create(
        db,
        format!("User {} tries to create log entry={:?}", &user.name, data),
    )
    .await;

    create_logbook(db, data, &user).await
}

#[post("/", data = "<data>")]
async fn create_kiosk(
    db: &State<SqlitePool>,
    data: Form<LogToAdd>,
    _kiosk: KioskCookie,
) -> Flash<Redirect> {
    let Some(boat) = Boat::find_by_id(db, data.boat_id).await else {
        return Flash::error(Redirect::to("/log"), "Boot gibt's nicht");
    };
    let creator = if boat.amount_seats == 1 && boat.owner.is_some() {
        User::find_by_id(db, boat.owner.unwrap() as i32)
            .await
            .unwrap()
    } else if let Some(shipmaster) = data.shipmaster {
        User::find_by_id(db, shipmaster as i32).await.unwrap()
    } else {
        User::find_by_id(db, data.rowers[0] as i32).await.unwrap()
    };
    Log::create(
        db,
        format!(
            "Kiosk tries to create log for shipmaster {} entry={:?}",
            creator.name, data
        ),
    )
    .await;

    create_logbook(db, data, &DonauLinzUser(creator)).await //TODO: fixme
}

async fn home_logbook(
    db: &SqlitePool,
    data: Form<LogToFinalize>,
    logbook_id: i32,
    user: &DonauLinzUser,
) -> Flash<Redirect> {
    let logbook: Option<Logbook> = Logbook::find_by_id(db, logbook_id).await;
    let Some(logbook) = logbook else {
        return Flash::error(
            Redirect::to("/admin/log"),
            format!("Log with ID {} does not exist!", logbook_id),
        );
    };

    match logbook.home(db,user, data.into_inner()).await {
        Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt korrekt eingetragen"),
        Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")),
        Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die heute enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten Philipp (Tel. nr. siehe Signal oder it@rudernlinz.at)."),
        Err(e) => Flash::error(
            Redirect::to("/log"),
            format!("Eintrag {logbook_id} konnte nicht abgesendet werden (Fehler: {e:?})!"),
        ),
    }
}

#[post("/<logbook_id>", data = "<data>")]
async fn home_kiosk(
    db: &State<SqlitePool>,
    data: Form<LogToFinalize>,
    logbook_id: i32,
    _kiosk: KioskCookie,
) -> Flash<Redirect> {
    let logbook = Logbook::find_by_id(db, logbook_id).await.unwrap(); //TODO: fixme

    Log::create(
        db,
        format!("Kiosk tries to finish log entry {logbook_id} {data:?}"),
    )
    .await;

    home_logbook(
        db,
        data,
        logbook_id,
        &DonauLinzUser(
            User::find_by_id(db, logbook.shipmaster as i32)
                .await
                .unwrap(),
        ), //TODO: fixme
    )
    .await
}

#[post("/<logbook_id>", data = "<data>", rank = 2)]
async fn home(
    db: &State<SqlitePool>,
    data: Form<LogToFinalize>,
    logbook_id: i32,
    user: DonauLinzUser,
) -> Flash<Redirect> {
    Log::create(
        db,
        format!(
            "User {} tries to finish log entry {logbook_id} {data:?}",
            &user.name
        ),
    )
    .await;

    home_logbook(db, data, logbook_id, &user).await
}

#[get("/<logbook_id>/delete", rank = 2)]
async fn delete(db: &State<SqlitePool>, logbook_id: i32, user: DonauLinzUser) -> Flash<Redirect> {
    let logbook = Logbook::find_by_id(db, logbook_id).await;
    if let Some(logbook) = logbook {
        Log::create(
            db,
            format!("User {} tries to delete log entry {logbook_id}", &user.name),
        )
        .await;
        match logbook.delete(db, &user).await {
            Ok(_) => Flash::success(
                Redirect::to("/log"),
                format!("Eintrag {} gelöscht!", logbook_id),
            ),
            Err(LogbookDeleteError::NotYourEntry) => Flash::error(
                Redirect::to("/log"),
                "Du hast nicht die Berechtigung, den Eintrag zu löschen!",
            ),
        }
    } else {
        Flash::error(
            Redirect::to("/log"),
            format!("Logbook with ID {} could not be found!", logbook_id),
        )
    }
}

#[get("/<logbook_id>/delete")]
async fn delete_kiosk(
    db: &State<SqlitePool>,
    logbook_id: i32,
    _kiosk: KioskCookie,
) -> Flash<Redirect> {
    let logbook = Logbook::find_by_id(db, logbook_id).await;
    if let Some(logbook) = logbook {
        let cox = User::find_by_id(db, logbook.shipmaster as i32)
            .await
            .unwrap();
        Log::create(db, format!("Kiosk tries to delete log entry {logbook_id} ")).await;
        match logbook.delete(db, &cox).await {
            Ok(_) => Flash::success(
                Redirect::to("/log"),
                format!("Eintrag {} gelöscht!", logbook_id),
            ),
            Err(LogbookDeleteError::NotYourEntry) => Flash::error(
                Redirect::to("/log"),
                "Du hast nicht die Berechtigung, den Eintrag zu löschen!",
            ),
        }
    } else {
        Flash::error(
            Redirect::to("/log"),
            format!("Logbook with ID {} could not be found!", logbook_id),
        )
    }
}

pub fn routes() -> Vec<Route> {
    routes![
        index,
        create,
        create_kiosk,
        home,
        kiosk,
        home_kiosk,
        new_kiosk,
        show,
        show_kiosk,
        show_for_year,
        delete,
        delete_kiosk
    ]
}

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

    use crate::model::logbook::Logbook;
    use crate::tera::{log::Boat, User};
    use crate::testdb;

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

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

        let client = Client::tracked(rocket).await.unwrap();
        let req = client.get("/log");
        let response = req.dispatch().await;

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

        let req = client.get("/log/kiosk/ekrv2019/Linz");
        let response = req.dispatch().await;

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

        let req = client.get("/log");
        let response = req.dispatch().await;

        assert_eq!(response.status(), Status::Ok);
        let text = response.into_string().await.unwrap();
        assert!(text.contains("Logbuch"));
        assert!(text.contains("Neue Ausfahrt"));

        //assert!(!text.contains("Ottensheim Boot"));
    }

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

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

        let client = Client::tracked(rocket).await.unwrap();
        let req = client.get("/log/kiosk/ekrv2019/Ottensheim");
        let response = req.dispatch().await;

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

        let req = client.get("/log");
        let response = req.dispatch().await;

        assert_eq!(response.status(), Status::Ok);
        let text = response.into_string().await.unwrap();
        assert!(text.contains("Logbuch"));
        assert!(text.contains("Neue Ausfahrt"));

        assert!(text.contains("Ottensheim Boot"));
    }

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

        let req = client.get("/log");
        let response = req.dispatch().await;

        let text = response.into_string().await.unwrap();
        assert!(text.contains("Logbuch"));
        assert!(text.contains("Neue Ausfahrt"));
    }

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

        let req = client.get("/log/show");
        let response = req.dispatch().await;

        let text = response.into_string().await.unwrap();
        println!("{text:?}");
        assert!(text.contains("Logbuch"));
        assert!(text.contains("Joe"));
    }

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

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

        let client = Client::tracked(rocket).await.unwrap();
        let req = client.get("/log/kiosk/ekrv2019/Linz");
        let _ = req.dispatch().await;

        let req = client.get("/log/show");
        let response = req.dispatch().await;

        let text = response.into_string().await.unwrap();
        assert!(text.contains("Logbuch"));
        assert!(text.contains("Joe"));
    }

    #[sqlx::test]
    fn test_create() {
        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=admin&password=admin"); // Add the form data to the request body;
        login.dispatch().await;
        let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();

        let req = client.post("/log").header(ContentType::Form).body(format!(
            "boat_id=1&shipmaster=4&departure={0}T10:00&steering_person=4&rowers[]=4",
            current_date
        ));
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "7:successAusfahrt erfolgreich hinzugefügt"
        );
    }

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

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

        let client = Client::tracked(rocket).await.unwrap();
        let req = client.get("/log/kiosk/ekrv2019/Linz");
        let _ = req.dispatch().await;

        let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();

        let req = client
            .post("/log/1")
            .header(ContentType::Form)
            .body(format!("destination=Ottensheim&distance_in_km=25&shipmaster=2&steering_person=2&departure={0}T10:00&arrival={0}T12:00&rowers[]=2", current_date));
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "7:successAusfahrt korrekt eingetragen"
        );
    }

    //Kiosk mode
    // i see all boats
    #[sqlx::test]
    fn test_kiosks_sees_all_boats() {
        let db = testdb!();

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

        let client = Client::tracked(rocket).await.unwrap();
        let req = client.get("/log/kiosk/ekrv2019/Linz");
        let _ = req.dispatch().await;

        let req = client.get("/log");
        let response = req.dispatch().await;

        let text = response.into_string().await.unwrap();
        //Sees all boats stationed in Linz
        assert!(text.contains("Haichenbach"));
        assert!(text.contains("Joe"));
        assert!(text.contains("Kaputtes Boot :-("));
        assert!(text.contains("Sehr kaputtes Boot :-(("));
        assert!(text.contains("second_private_boat_from_rower"));
        assert!(text.contains("private_boat_from_rower"));

        //Doesn't see the one's in Ottensheim
        //assert!(!text.contains("Ottensheim Boot"));
    }

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

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

        sqlx::query("DELETE FROM logbook;")
            .execute(&db)
            .await
            .unwrap();

        let mut client = Client::tracked(rocket).await.unwrap();
        let req = client.get("/log/kiosk/ekrv2019/Linz");
        let _ = req.dispatch().await;

        can_start_and_end_trip(&db, &mut client, "Haichenbach".into(), "admin".into()).await;
        can_start_and_end_trip(&db, &mut client, "Joe".into(), "admin".into()).await;
        can_start_and_end_trip(&db, &mut client, "Kaputtes Boot :-(".into(), "admin".into()).await;
        cant_start_trip(
            &db,
            &mut client,
            "Sehr kaputtes Boot :-((".into(),
            "admin".into(),
            "Boot gesperrt".into(),
        )
        .await;
        can_start_and_end_trip(
            &db,
            &mut client,
            "second_private_boat_from_rower".into(),
            "rower".into(),
        )
        .await;
    }

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

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

        sqlx::query("DELETE FROM logbook;")
            .execute(&db)
            .await
            .unwrap();

        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;

        // Owner can start trip:
        let boat_id = Boat::find_by_name(&db, "private_boat_from_rower".into())
            .await
            .unwrap()
            .id;
        let shipmaster_id = User::find_by_name(&db, "rower2".into()).await.unwrap().id;
        let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();

        let req = client.post("/log").header(ContentType::Form).body(format!(
            "boat_id={boat_id}&shipmaster={shipmaster_id}&departure={0}T10:00&steering_person={shipmaster_id}&rowers[]={shipmaster_id}", current_date
        ));
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "7:successAusfahrt erfolgreich hinzugefügt"
        );

        // Shipmaster can end it
        let log_id = Logbook::highest_id(&db).await;

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

        let req = client
            .post(format!("/log/{log_id}"))
            .header(ContentType::Form)
            .body(format!("destination=Ottensheim&distance_in_km=25&shipmaster={shipmaster_id}&steering_person={shipmaster_id}&departure={0}T10:00&arrival={0}T12:00&rowers[]={shipmaster_id}", current_date));
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "7:successAusfahrt korrekt eingetragen"
        );
    }

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

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

        let mut 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("/log");
        let response = req.dispatch().await;

        let text = response.into_string().await.unwrap();

        sqlx::query("DELETE FROM logbook;")
            .execute(&db)
            .await
            .unwrap();

        //Sees all 1x
        assert!(text.contains("Haichenbach"));
        can_start_and_end_trip(&db, &mut client, "Haichenbach".into(), "rower".into()).await;

        assert!(text.contains("private_boat_from_rower"));
        can_start_and_end_trip(
            &db,
            &mut client,
            "private_boat_from_rower".into(),
            "rower".into(),
        )
        .await;

        assert!(text.contains("second_private_boat_from_rower"));
        can_start_and_end_trip(
            &db,
            &mut client,
            "second_private_boat_from_rower".into(),
            "rower".into(),
        )
        .await;

        //Don't see anything else
        assert!(!text.contains("Joe"));
        cant_start_trip(
            &db,
            &mut client,
            "Joe".into(),
            "rower".into(),
            "Schiffsführer darf dieses Boot nicht verwenden".into(),
        )
        .await;

        assert!(!text.contains("Kaputtes Boot :-("));
        cant_start_trip(
            &db,
            &mut client,
            "Kaputtes Boot :-(".into(),
            "rower".into(),
            "Schiffsführer darf dieses Boot nicht verwenden".into(),
        )
        .await;

        assert!(!text.contains("Sehr kaputtes Boot :-(("));
        cant_start_trip(
            &db,
            &mut client,
            "Sehr kaputtes Boot :-((".into(),
            "rower".into(),
            "Boot gesperrt".into(),
        )
        .await;

        assert!(!text.contains("Ottensheim Boot"));
        cant_start_trip(
            &db,
            &mut client,
            "Ottensheim Boot".into(),
            "rower".into(),
            "Schiffsführer darf dieses Boot nicht verwenden".into(),
        )
        .await;
    }

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

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

        let mut 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;

        sqlx::query("DELETE FROM logbook;")
            .execute(&db)
            .await
            .unwrap();

        let req = client.get("/log");
        let response = req.dispatch().await;

        let text = response.into_string().await.unwrap();

        //Sees all 1x
        assert!(text.contains("Haichenbach"));
        can_start_and_end_trip(&db, &mut client, "Haichenbach".into(), "cox".into()).await;

        assert!(text.contains("Joe"));
        can_start_and_end_trip(&db, &mut client, "Joe".into(), "cox".into()).await;

        assert!(text.contains("Kaputtes Boot :-("));
        can_start_and_end_trip(&db, &mut client, "Kaputtes Boot :-(".into(), "cox".into()).await;

        assert!(text.contains("Sehr kaputtes Boot :-(("));
        cant_start_trip(
            &db,
            &mut client,
            "Sehr kaputtes Boot :-((".into(),
            "cox".into(),
            "Boot gesperrt".into(),
        )
        .await;

        assert!(text.contains("Ottensheim Boot"));
        can_start_and_end_trip(&db, &mut client, "Ottensheim Boot".into(), "cox".into()).await;

        //Can't use private boats
        assert!(!text.contains("private_boat_from_rower"));
        cant_start_trip(
            &db,
            &mut client,
            "private_boat_from_rower".into(),
            "cox".into(),
            "Schiffsführer darf dieses Boot nicht verwenden".into(),
        )
        .await;

        assert!(!text.contains("second_private_boat_from_rower"));
        cant_start_trip(
            &db,
            &mut client,
            "second_private_boat_from_rower".into(),
            "cox".into(),
            "Schiffsführer darf dieses Boot nicht verwenden".into(),
        )
        .await;
    }

    #[sqlx::test]
    fn test_cant_end_trip_other_user() {
        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=rower2&password=rower"); // Add the form data to the request body;
        login.dispatch().await;
        let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();

        let req = client
            .post("/log/1")
            .header(ContentType::Form)
            .body(format!("destination=Ottensheim&distance_in_km=25&shipmaster=1&steering_person=1&departure={0}T10:00&arrival={0}T12:00&rowers[]=1", current_date));
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "5:errorEintrag 1 konnte nicht abgesendet werden (Fehler: NotYourEntry)!"
        );
    }

    async fn can_start_and_end_trip(
        db: &SqlitePool,
        client: &mut Client,
        boat_name: String,
        shipmaster_name: String,
    ) {
        let boat_id = Boat::find_by_name(db, boat_name).await.unwrap().id;
        let shipmaster_id = User::find_by_name(db, &shipmaster_name).await.unwrap().id;

        let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();

        let req = client.post("/log").header(ContentType::Form).body(format!(
            "boat_id={boat_id}&shipmaster={shipmaster_id}&departure={current_date}T10:00&steering_person={shipmaster_id}&rowers[]={shipmaster_id}"
        ));
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "7:successAusfahrt erfolgreich hinzugefügt"
        );

        let log_id = Logbook::highest_id(db).await;

        let req = client
            .post(format!("/log/{log_id}"))
            .header(ContentType::Form)
            .body(format!("destination=Ottensheim&distance_in_km=25&shipmaster={shipmaster_id}&steering_person={shipmaster_id}&departure={current_date}T10:00&arrival={current_date}T12:00&rowers[]={shipmaster_id}"));
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "7:successAusfahrt korrekt eingetragen"
        );
    }

    async fn cant_start_trip(
        db: &SqlitePool,
        client: &mut Client,
        boat_name: String,
        shipmaster_name: String,
        reason: String,
    ) {
        let boat_id = Boat::find_by_name(db, boat_name).await.unwrap().id;
        let shipmaster_id = User::find_by_name(db, &shipmaster_name).await.unwrap().id;

        let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();

        let req = client.post("/log").header(ContentType::Form).body(format!(
            "boat_id={boat_id}&shipmaster={shipmaster_id}&departure={current_date}T10:00&steering_person={shipmaster_id}&rowers[]={shipmaster_id}"
        ));
        let response = req.dispatch().await;

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

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

        assert_eq!(flash_cookie.value(), format!("5:error{}", reason));
    }
}
