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

use crate::model::{
    planned_event::PlannedEvent,
    tripdetails::{TripDetails, TripDetailsToAdd},
    user::PlannedEventUser,
};

//TODO: add constraints (e.g. planned_amount_cox > 0)
#[derive(FromForm, Serialize)]
struct AddPlannedEventForm<'r> {
    name: &'r str,
    planned_amount_cox: i32,
    tripdetails: TripDetailsToAdd<'r>,
}

#[post("/planned-event", data = "<data>")]
async fn create(
    db: &State<SqlitePool>,
    data: Form<AddPlannedEventForm<'_>>,
    _admin: PlannedEventUser,
) -> Flash<Redirect> {
    let data = data.into_inner();

    let trip_details_id = TripDetails::create(db, data.tripdetails).await;
    let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc. we
                                                                                    //just created
                                                                                    //the object

    PlannedEvent::create(db, data.name, data.planned_amount_cox, trip_details).await;

    Flash::success(Redirect::to("/planned"), "Event hinzugefügt")
}

//TODO: add constraints (e.g. planned_amount_cox > 0)
#[derive(FromForm)]
struct UpdatePlannedEventForm<'r> {
    id: i64,
    name: &'r str,
    planned_amount_cox: i32,
    max_people: i32,
    notes: Option<&'r str>,
    always_show: bool,
    is_locked: bool,
}

#[put("/planned-event", data = "<data>")]
async fn update(
    db: &State<SqlitePool>,
    data: Form<UpdatePlannedEventForm<'_>>,
    _admin: PlannedEventUser,
) -> Flash<Redirect> {
    match PlannedEvent::find_by_id(db, data.id).await {
        Some(planned_event) => {
            planned_event
                .update(
                    db,
                    data.name,
                    data.planned_amount_cox,
                    data.max_people,
                    data.notes,
                    data.always_show,
                    data.is_locked,
                )
                .await;
            Flash::success(Redirect::to("/planned"), "Event erfolgreich bearbeitet")
        }
        None => Flash::error(Redirect::to("/planned"), "Planned event id not found"),
    }
}

#[get("/planned-event/<id>/delete")]
async fn delete(db: &State<SqlitePool>, id: i64, _admin: PlannedEventUser) -> Flash<Redirect> {
    match PlannedEvent::find_by_id(db, id).await {
        Some(planned_event) => {
            planned_event.delete(db).await;
            Flash::success(Redirect::to("/planned"), "Event gelöscht")
        }
        None => Flash::error(Redirect::to("/planned"), "PlannedEvent does not exist"),
    }
}

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

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

    use super::*;
    use crate::testdb;

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

        let _ = PlannedEvent::find_by_id(&db, 1).await.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=admin&password=admin"); // Add the form data to the request body;
        login.dispatch().await;

        let req = client.get("/admin/planned-event/1/delete");
        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:successEvent gelöscht");

        let event = PlannedEvent::find_by_id(&db, 1).await;
        assert_eq!(event, None);
    }

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

        let req = client.get("/admin/planned-event/1337/delete");
        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:errorPlannedEvent does not exist");

        let _ = PlannedEvent::find_by_id(&db, 1).await.unwrap();
    }

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

        let event = PlannedEvent::find_by_id(&db, 1).await.unwrap();
        assert_eq!(event.notes, Some("trip_details for a planned event".into()));

        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
            .put("/admin/planned-event")
            .header(ContentType::Form) // Set the content type to form
            .body("id=1&planned_amount_cox=2&max_people=3&notes=new-planned-event-text&name=test"); // Add the form data to the request body;
        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:successEvent erfolgreich bearbeitet"
        );

        let event = PlannedEvent::find_by_id(&db, 1).await.unwrap();
        assert_eq!(event.notes, Some("new-planned-event-text".into()));
    }

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

        let req = client
            .put("/admin/planned-event")
            .header(ContentType::Form) // Set the content type to form
            .body(
                "id=1337&planned_amount_cox=2&max_people=3&notes=new-planned-event-text&name=test",
            ); // Add the form data to the request body;
        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 id not found");
    }

    #[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 req = client
            .post("/admin/planned-event")
            .header(ContentType::Form) // Set the content type to form
            .body("name=my-cool-new-event&planned_amount_cox=42&tripdetails.planned_starting_time=10:01&tripdetails.max_people=3&tripdetails.day=2345-12-20"); // Add the form data to the request body;
        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:successEvent hinzugefügt");

        let event = PlannedEvent::find_by_id(&db, 2).await.unwrap();
        assert_eq!(event.name, "my-cool-new-event");
    }
}