use crate::model::{
    boat::{Boat, BoatToAdd, BoatToUpdate},
    location::Location,
    user::{AdminUser, User, UserWithRoles},
};
use rocket::{
    form::Form,
    get, post,
    request::FlashMessage,
    response::{Flash, Redirect},
    routes, Route, State,
};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;

#[get("/boat")]
async fn index(
    db: &State<SqlitePool>,
    admin: AdminUser,
    flash: Option<FlashMessage<'_>>,
) -> Template {
    let boats = Boat::all(db).await;
    let locations = Location::all(db).await;
    let users = User::all(db).await;

    let mut context = Context::new();
    if let Some(msg) = flash {
        context.insert("flash", &msg.into_inner());
    }
    context.insert("boats", &boats);
    context.insert("locations", &locations);
    context.insert("users", &users);
    context.insert(
        "loggedin_user",
        &UserWithRoles::from_user(admin.user, db).await,
    );

    Template::render("admin/boat/index", context.into_json())
}

#[get("/boat/<boat>/delete")]
async fn delete(db: &State<SqlitePool>, _admin: AdminUser, boat: i32) -> Flash<Redirect> {
    let boat = Boat::find_by_id(db, boat).await;
    match boat {
        Some(boat) => {
            boat.delete(db).await;
            Flash::success(
                Redirect::to("/admin/boat"),
                format!("Boot {} gelöscht", boat.name),
            )
        }
        None => Flash::error(Redirect::to("/admin/boat"), "Boat does not exist"),
    }
}

#[post("/boat/<boat_id>", data = "<data>")]
async fn update(
    db: &State<SqlitePool>,
    data: Form<BoatToUpdate<'_>>,
    boat_id: i32,
    _admin: AdminUser,
) -> Flash<Redirect> {
    let boat = Boat::find_by_id(db, boat_id).await;
    let Some(boat) = boat else {
        return Flash::error(Redirect::to("/admin/boat"), "Boat does not exist!");
    };

    match boat.update(db, data.into_inner()).await {
        Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Boot bearbeitet"),
        Err(e) => Flash::error(Redirect::to("/admin/boat"), e),
    }
}

#[post("/boat/new", data = "<data>")]
async fn create(
    db: &State<SqlitePool>,
    data: Form<BoatToAdd<'_>>,
    _admin: AdminUser,
) -> Flash<Redirect> {
    match Boat::create(db, data.into_inner()).await {
        Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Boot hinzugefügt"),
        Err(e) => Flash::error(Redirect::to("/admin/boat"), e),
    }
}

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

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

    use crate::tera::admin::boat::Boat;
    use crate::testdb;

    #[sqlx::test]
    fn test_boat_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("/admin/boat");
        let response = req.dispatch().await;

        assert_eq!(response.status(), Status::Ok);
        let text = response.into_string().await.unwrap();
        assert!(&text.contains("Neues Boot"));
        assert!(&text.contains("Kaputtes Boot :-("));
        assert!(&text.contains("Haichenbach"));
    }

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

        let boat = Boat::find_by_id(&db, 1).await.unwrap();
        assert_eq!(boat.name, "Haichenbach");

        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
            .post("/admin/boat/1")
            .header(ContentType::Form)
            .body("name=Haichiii&amount_seats=1&location_id=1");
        let response = req.dispatch().await;

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

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

        assert_eq!(flash_cookie.value(), "7:successBoot bearbeitet");

        let boat = Boat::find_by_id(&db, 1).await.unwrap();
        assert_eq!(boat.name, "Haichiii");
    }

    #[sqlx::test]
    fn test_update_wrong_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 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
            .post("/admin/boat/1337")
            .header(ContentType::Form)
            .body("name=Haichiii&amount_seats=1&location_id=1");
        let response = req.dispatch().await;

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

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

        assert_eq!(flash_cookie.value(), "5:errorBoat does not exist!");

        let boat = Boat::find_by_id(&db, 1).await.unwrap();
        assert_eq!(boat.name, "Haichenbach");
    }

    #[sqlx::test]
    fn test_update_wrong_foreign() {
        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
            .post("/admin/boat/1")
            .header(ContentType::Form)
            .body("name=Haichiii&amount_seats=1&location_id=999");
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "5:errorerror returned from database: (code: 787) FOREIGN KEY constraint failed"
        );
    }

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

        let rocket = rocket::build().manage(db.clone());
        let rocket = crate::tera::config(rocket);
        assert!(Boat::find_by_name(&db, "completely-new-boat".into())
            .await
            .is_none());

        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
            .post("/admin/boat/new")
            .header(ContentType::Form)
            .body("name=completely-new-boat&amount_seats=1&location_id=1");
        let response = req.dispatch().await;

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

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

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

        Boat::find_by_name(&db, "completely-new-boat".into())
            .await
            .unwrap();
    }

    #[sqlx::test]
    fn test_create_db_error() {
        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
            .post("/admin/boat/new")
            .header(ContentType::Form)
            .body("name=Haichenbach&amount_seats=1&location_id=1");
        let response = req.dispatch().await;

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

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

        assert_eq!(
            flash_cookie.value(),
            "5:errorerror returned from database: (code: 2067) UNIQUE constraint failed: boat.name"
        );
    }
}
