use sqlx::SqlitePool;

use super::{notification::Notification, trip::Trip, tripdetails::TripDetails, user::User};
use crate::model::tripdetails::{Action, CoxAtTrip::Yes};

pub struct UserTrip {}

impl UserTrip {
    pub async fn create(
        db: &SqlitePool,
        user: &User,
        trip_details: &TripDetails,
        user_note: Option<String>,
    ) -> Result<String, UserTripError> {
        if trip_details.is_full(db).await {
            return Err(UserTripError::EventAlreadyFull);
        }

        if trip_details.is_locked {
            return Err(UserTripError::DetailsLocked);
        }

        if user.has_role(db, "scheckbuch").await && !trip_details.allow_guests {
            return Err(UserTripError::GuestNotAllowedForThisEvent);
        }

        //TODO: Check if user sees the event (otherwise she could forge trip_details_id)

        let is_cox = trip_details.user_is_cox(db, user).await;
        let mut name_newly_registered_person = String::new();
        if user_note.is_none() {
            if let Yes(action) = is_cox {
                match action {
                    Action::Helping => return Err(UserTripError::AlreadyRegisteredAsCox),
                    Action::Own => return Err(UserTripError::CantRegisterAtOwnEvent),
                };
            }

            if trip_details.user_is_rower(db, user).await {
                return Err(UserTripError::AlreadyRegistered);
            }

            sqlx::query!(
                "INSERT INTO user_trip (user_id, trip_details_id) VALUES(?, ?)",
                user.id,
                trip_details.id,
            )
            .execute(db)
            .await
            .unwrap();

            name_newly_registered_person = user.name.clone();
        } else {
            if !trip_details.user_allowed_to_change(db, user).await {
                return Err(UserTripError::NotAllowedToAddGuest);
            }
            sqlx::query!(
                "INSERT INTO user_trip (user_note, trip_details_id) VALUES(?, ?)",
                user_note,
                trip_details.id,
            )
            .execute(db)
            .await
            .unwrap();

            name_newly_registered_person = user_note.unwrap();
        }

        if let Some(trip) = Trip::find_by_trip_details(db, trip_details.id).await {
            let cox = User::find_by_id(db, trip.cox_id as i32).await.unwrap();
            Notification::create(
                db,
                &cox,
                &format!(
                    "{} hat sich für deine Ausfahrt am {} registriert",
                    name_newly_registered_person, trip.day
                ),
                "Registrierung bei deiner Ausfahrt",
                None,
            )
            .await;
        }

        Ok(name_newly_registered_person)
    }

    pub async fn delete(
        db: &SqlitePool,
        user: &User,
        trip_details: &TripDetails,
        name: Option<String>,
    ) -> Result<(), UserTripDeleteError> {
        if trip_details.is_locked {
            return Err(UserTripDeleteError::DetailsLocked);
        }

        if let Some(name) = name {
            if !trip_details.user_allowed_to_change(db, user).await {
                return Err(UserTripDeleteError::NotAllowedToDeleteGuest);
            }

            if sqlx::query!(
                "DELETE FROM user_trip WHERE user_note = ? AND trip_details_id = ?",
                name,
                trip_details.id
            )
            .execute(db)
            .await
            .unwrap()
            .rows_affected()
                == 0
            {
                return Err(UserTripDeleteError::GuestNotParticipating);
            }
        } else {
            let _ = sqlx::query!(
                "DELETE FROM user_trip WHERE user_id = ? AND trip_details_id = ?",
                user.id,
                trip_details.id
            )
            .execute(db)
            .await
            .unwrap();
        }
        Ok(())
    }
}

#[derive(Debug, PartialEq)]
pub enum UserTripError {
    AlreadyRegistered,
    AlreadyRegisteredAsCox,
    EventAlreadyFull,
    DetailsLocked,
    CantRegisterAtOwnEvent,
    GuestNotAllowedForThisEvent,
    NotAllowedToAddGuest,
}

#[derive(Debug, PartialEq)]
pub enum UserTripDeleteError {
    DetailsLocked,
    GuestNotParticipating,
    NotAllowedToDeleteGuest,
}

#[cfg(test)]
mod test {
    use crate::{
        model::{
            planned_event::PlannedEvent, trip::Trip, tripdetails::TripDetails, user::CoxUser,
            usertrip::UserTripError,
        },
        testdb,
    };

    use super::{User, UserTrip};
    use sqlx::SqlitePool;

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

        let user = User::find_by_name(&pool, "rower".into()).await.unwrap();

        let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();

        UserTrip::create(&pool, &user, &trip_details, None)
            .await
            .unwrap();
    }

    #[sqlx::test]
    fn test_fail_create_full() {
        let pool = testdb!();

        let user = User::find_by_name(&pool, "rower".into()).await.unwrap();
        let user2 = User::find_by_name(&pool, "cox".into()).await.unwrap();
        let user3 = User::find_by_name(&pool, "rower2".into()).await.unwrap();

        let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();

        UserTrip::create(&pool, &user, &trip_details, None)
            .await
            .unwrap();
        UserTrip::create(&pool, &user2, &trip_details, None)
            .await
            .unwrap();

        let result = UserTrip::create(&pool, &user3, &trip_details, None)
            .await
            .expect_err("Expect registration to fail because trip is already full");

        assert_eq!(result, UserTripError::EventAlreadyFull);
    }

    #[sqlx::test]
    fn test_fail_create_already_registered() {
        let pool = testdb!();

        let user = User::find_by_name(&pool, "cox".into()).await.unwrap();
        let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();

        UserTrip::create(&pool, &user, &trip_details, None)
            .await
            .unwrap();

        let result = UserTrip::create(&pool, &user, &trip_details, None)
            .await
            .expect_err("Expect registration to fail because user is same as responsible cox");

        assert_eq!(result, UserTripError::AlreadyRegistered);
    }

    #[sqlx::test]
    fn test_fail_create_is_cox_own_trip() {
        let pool = testdb!();

        let user = User::find_by_name(&pool, "cox".into()).await.unwrap();

        let trip_details = TripDetails::find_by_id(&pool, 2).await.unwrap();

        let result = UserTrip::create(&pool, &user, &trip_details, None)
            .await
            .expect_err("Expect registration to fail because user is same as responsible cox");

        assert_eq!(result, UserTripError::CantRegisterAtOwnEvent);
    }

    #[sqlx::test]
    fn test_fail_create_is_cox_planned_event() {
        let pool = testdb!();

        let cox = CoxUser::new(
            &pool,
            User::find_by_name(&pool, "cox".into()).await.unwrap(),
        )
        .await
        .unwrap();

        let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap();
        Trip::new_join(&pool, &cox, &planned_event).await.unwrap();

        let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
        let result = UserTrip::create(&pool, &cox, &trip_details, None)
            .await
            .expect_err("Expect registration to fail because user is already registered as cox");

        assert_eq!(result, UserTripError::AlreadyRegisteredAsCox);
    }

    #[sqlx::test]
    fn test_fail_create_guest() {
        let pool = testdb!();

        let user = User::find_by_name(&pool, "guest".into()).await.unwrap();

        let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();

        let result = UserTrip::create(&pool, &user, &trip_details, None)
            .await
            .expect_err("Not allowed for guests");

        assert_eq!(result, UserTripError::GuestNotAllowedForThisEvent);
    }
}
