use chrono::NaiveDate;
use serde::Serialize;
use sqlx::SqlitePool;

use super::{
    notification::Notification,
    planned_event::{PlannedEvent, Registration},
    tripdetails::TripDetails,
    triptype::TripType,
    user::{CoxUser, User},
};

#[derive(Serialize, Clone, Debug)]
pub struct Trip {
    id: i64,
    pub cox_id: i64,
    cox_name: String,
    trip_details_id: Option<i64>,
    planned_starting_time: String,
    pub max_people: i64,
    pub day: String,
    pub notes: Option<String>,
    pub allow_guests: bool,
    trip_type_id: Option<i64>,
    always_show: bool,
    is_locked: bool,
}

#[derive(Serialize, Debug)]
pub struct TripWithUserAndType {
    #[serde(flatten)]
    pub trip: Trip,
    rower: Vec<Registration>,
    trip_type: Option<TripType>,
}

impl Trip {
    /// Cox decides to create own trip.
    pub async fn new_own(db: &SqlitePool, cox: &CoxUser, trip_details: TripDetails) {
        let _ = sqlx::query!(
            "INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)",
            cox.id,
            trip_details.id
        )
        .execute(db)
        .await;

        let same_starting_datetime = TripDetails::find_by_startingdatetime(
            db,
            trip_details.day,
            trip_details.planned_starting_time,
        )
        .await;
        if same_starting_datetime.len() > 1 {
            for notify in same_starting_datetime {
                if notify.id != trip_details.id {
                    // notify everyone except oneself
                    if let Some(trip) = Trip::find_by_trip_details(db, notify.id).await {
                        let user = User::find_by_id(db, trip.cox_id as i32).await.unwrap();
                        Notification::create(
                            db,
                            &user,
                            &format!(
                                "{} hat eine Ausfahrt zur selben Zeit ({} um {}) wie du erstellt",
                                cox.user.name, trip.day, trip.planned_starting_time
                            ),
                            "Neue Ausfahrt zur selben Zeit".into(),
                            None,
                        )
                        .await;
                    }
                }
            }
        }
    }

    pub async fn find_by_trip_details(db: &SqlitePool, tripdetails_id: i64) -> Option<Self> {
        sqlx::query_as!(
            Self,
            "
SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked
FROM trip 
INNER JOIN trip_details ON trip.trip_details_id = trip_details.id
INNER JOIN user ON trip.cox_id = user.id
WHERE trip_details.id=?
        ",
            tripdetails_id
        )
        .fetch_one(db)
        .await
        .ok()
    }

    pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
        sqlx::query_as!(
            Self,
            "
SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked
FROM trip 
INNER JOIN trip_details ON trip.trip_details_id = trip_details.id
INNER JOIN user ON trip.cox_id = user.id
WHERE trip.id=?
        ",
            id
        )
        .fetch_one(db)
        .await
        .ok()
    }

    /// Cox decides to help in a planned event.
    pub async fn new_join(
        db: &SqlitePool,
        cox: &CoxUser,
        planned_event: &PlannedEvent,
    ) -> Result<(), CoxHelpError> {
        if planned_event.is_rower_registered(db, cox).await {
            return Err(CoxHelpError::AlreadyRegisteredAsRower);
        }

        if planned_event.trip_details(db).await.is_locked {
            return Err(CoxHelpError::DetailsLocked);
        }

        match sqlx::query!(
            "INSERT INTO trip (cox_id, planned_event_id) VALUES(?, ?)",
            cox.id,
            planned_event.id
        )
        .execute(db)
        .await
        {
            Ok(_) => Ok(()),
            Err(_) => Err(CoxHelpError::AlreadyRegisteredAsCox),
        }
    }

    pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<TripWithUserAndType> {
        let day = format!("{day}");
        let trips = sqlx::query_as!(
            Trip,
            "
SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked
FROM trip 
INNER JOIN trip_details ON trip.trip_details_id = trip_details.id
INNER JOIN user ON trip.cox_id = user.id
WHERE day=?
    ",
            day
        )
        .fetch_all(db)
        .await
        .unwrap(); //Okay, as Trip can only be created with proper DB backing

        let mut ret = Vec::new();
        for trip in trips {
            let mut trip_type = None;
            if let Some(trip_type_id) = trip.trip_type_id {
                trip_type = TripType::find_by_id(db, trip_type_id).await;
            }
            ret.push(TripWithUserAndType {
                rower: Registration::all_rower(db, trip.trip_details_id.unwrap()).await,
                trip,
                trip_type,
            });
        }
        ret
    }

    /// Cox decides to update own trip.
    pub async fn update_own(
        db: &SqlitePool,
        cox: &CoxUser,
        trip: &Trip,
        max_people: i32,
        notes: Option<&str>,
        trip_type: Option<i64>, //TODO: Move to `TripType`
        always_show: bool,
        is_locked: bool,
    ) -> Result<(), TripUpdateError> {
        if !trip.is_trip_from_user(cox.id) {
            return Err(TripUpdateError::NotYourTrip);
        }

        let Some(trip_details_id) = trip.trip_details_id else {
            return Err(TripUpdateError::TripDetailsDoesNotExist); //TODO: Remove?
        };

        sqlx::query!(
            "UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ?, is_locked = ? WHERE id = ?",
            max_people,
            notes,
            trip_type,
            always_show,
            is_locked,
            trip_details_id
        )
        .execute(db)
        .await
        .unwrap(); //Okay, as trip_details can only be created with proper DB backing

        Ok(())
    }

    pub async fn trip_details(&self, db: &SqlitePool) -> Option<TripDetails> {
        if let Some(trip_details_id) = self.trip_type_id {
            return TripDetails::find_by_id(db, trip_details_id).await;
        }
        None
    }

    pub async fn delete_by_planned_event(
        db: &SqlitePool,
        cox: &CoxUser,
        planned_event: &PlannedEvent,
    ) -> Result<(), TripHelpDeleteError> {
        if planned_event.trip_details(db).await.is_locked {
            return Err(TripHelpDeleteError::DetailsLocked);
        }

        let affected_rows = sqlx::query!(
            "DELETE FROM trip WHERE cox_id = ? AND planned_event_id = ?",
            cox.id,
            planned_event.id
        )
        .execute(db)
        .await
        .unwrap()
        .rows_affected();

        if affected_rows == 0 {
            return Err(TripHelpDeleteError::CoxNotHelping);
        }

        Ok(())
    }

    pub(crate) async fn delete(
        &self,
        db: &SqlitePool,
        user: &CoxUser,
    ) -> Result<(), TripDeleteError> {
        let registered_rower = Registration::all_rower(db, self.trip_details_id.unwrap()).await;
        if !registered_rower.is_empty() {
            return Err(TripDeleteError::SomebodyAlreadyRegistered);
        }

        if !self.is_trip_from_user(user.id) {
            return Err(TripDeleteError::NotYourTrip);
        }

        sqlx::query!(
            "DELETE FROM trip WHERE cox_id = ? AND id = ?",
            user.id,
            self.id
        )
        .execute(db)
        .await
        .unwrap(); //TODO: fixme

        Ok(())
    }

    fn is_trip_from_user(&self, user_id: i64) -> bool {
        self.cox_id == user_id
    }

    pub(crate) async fn get_pinned_for_day(
        db: &sqlx::Pool<sqlx::Sqlite>,
        day: NaiveDate,
    ) -> Vec<TripWithUserAndType> {
        let mut trips = Self::get_for_day(db, day).await;
        trips.retain(|e| e.trip.always_show);
        trips
    }
}

#[derive(Debug)]
pub enum CoxHelpError {
    AlreadyRegisteredAsRower,
    AlreadyRegisteredAsCox,
    DetailsLocked,
}

#[derive(Debug, PartialEq)]
pub enum TripHelpDeleteError {
    DetailsLocked,
    CoxNotHelping,
}

#[derive(Debug, PartialEq)]
pub enum TripDeleteError {
    SomebodyAlreadyRegistered,
    NotYourTrip,
}

#[derive(Debug)]
pub enum TripUpdateError {
    NotYourTrip,
    TripDetailsDoesNotExist,
}

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

    use chrono::NaiveDate;
    use sqlx::SqlitePool;

    use super::Trip;

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

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

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

        Trip::new_own(&pool, &cox, trip_details).await;

        assert!(Trip::find_by_id(&pool, 1).await.is_some());
    }

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

        let res = Trip::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 2).unwrap()).await;
        assert_eq!(res.len(), 1);
    }

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

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

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

        assert!(Trip::new_join(&pool, &cox, &planned_event).await.is_ok());
    }

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

        let cox = CoxUser::new(
            &pool,
            User::find_by_name(&pool, "cox2".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();
        assert!(Trip::new_join(&pool, &cox, &planned_event).await.is_err());
    }

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

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

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();

        assert!(
            Trip::update_own(&pool, &cox, &trip, 10, None, None, false, false)
                .await
                .is_ok()
        );

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();
        assert_eq!(trip.max_people, 10);
    }

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

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

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();

        assert!(
            Trip::update_own(&pool, &cox, &trip, 10, None, Some(1), false, false)
                .await
                .is_ok()
        );

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();
        assert_eq!(trip.max_people, 10);
        assert_eq!(trip.trip_type_id, Some(1));
    }

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

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

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();

        assert!(
            Trip::update_own(&pool, &cox, &trip, 10, None, None, false, false)
                .await
                .is_err()
        );
        assert_eq!(trip.max_people, 1);
    }

    #[sqlx::test]
    fn test_succ_delete_by_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();

        //TODO: check why following assert fails
        //assert!(Trip::find_by_id(&pool, 2).await.is_some());
        Trip::delete_by_planned_event(&pool, &cox, &planned_event)
            .await
            .unwrap();
        assert!(Trip::find_by_id(&pool, 2).await.is_none());
    }

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

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

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();

        trip.delete(&pool, &cox).await.unwrap();

        assert!(Trip::find_by_id(&pool, 1).await.is_none());
    }

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

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

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();

        let result = trip
            .delete(&pool, &cox)
            .await
            .expect_err("It should not be possible to delete trips from others");
        let expected = TripDeleteError::NotYourTrip;

        assert_eq!(result, expected);
    }

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

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

        let trip = Trip::find_by_id(&pool, 1).await.unwrap();

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

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

        let result = trip
            .delete(&pool, &cox)
            .await
            .expect_err("It should not be possible to delete trips if somebody already registered");
        let expected = TripDeleteError::SomebodyAlreadyRegistered;

        assert_eq!(result, expected);
    }
}
