use crate::model::user::User;
use chrono::NaiveDate;
use rocket::FromForm;
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool};

#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct TripDetails {
    pub id: i64,
    pub planned_starting_time: String,
    pub max_people: i64,
    pub day: String,
    pub notes: Option<String>,
    pub allow_guests: bool,
    pub trip_type_id: Option<i64>,
    pub always_show: bool,
    pub is_locked: bool,
}

#[derive(FromForm, Serialize)]
pub struct TripDetailsToAdd<'r> {
    //TODO: properly parse `planned_starting_time`
    pub planned_starting_time: &'r str,
    pub max_people: i32,
    pub day: String,
    //#[field(validate = range(1..))] TODO: fixme
    pub notes: Option<&'r str>,
    pub trip_type: Option<i64>,
    pub allow_guests: bool,
    pub always_show: bool,
}

impl TripDetails {
    pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
        sqlx::query_as!(
            TripDetails,
            "
SELECT id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show, is_locked
FROM trip_details 
WHERE id like ?
        ",
            id
        )
        .fetch_one(db)
        .await
        .ok()
    }

    pub async fn find_by_startingdatetime(
        db: &SqlitePool,
        day: String,
        planned_starting_time: String,
    ) -> Vec<Self> {
        sqlx::query_as!(
            Self,
            "
SELECT id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show, is_locked
FROM trip_details 
WHERE day = ? AND planned_starting_time = ?
        "
        , day, planned_starting_time
        )
        .fetch_all(db)
        .await.unwrap()
    }

    /// Creates a new entry in `trip_details` and returns its id.
    pub async fn create(db: &SqlitePool, tripdetails: TripDetailsToAdd<'_>) -> i64 {
        let query = sqlx::query!(
            "INSERT INTO trip_details(planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show) VALUES(?, ?, ?, ?, ?, ?, ?)" ,
            tripdetails.planned_starting_time,
            tripdetails.max_people,
            tripdetails.day,
            tripdetails.notes,
            tripdetails.allow_guests,
            tripdetails.trip_type,
            tripdetails.always_show
        )
        .execute(db)
        .await
        .unwrap(); //Okay, TripDetails can only be created if self.id exists
        query.last_insert_rowid()
    }

    pub async fn is_full(&self, db: &SqlitePool) -> bool {
        let amount_currently_registered = sqlx::query!(
            "SELECT COUNT(*) as count FROM user_trip WHERE trip_details_id = ?",
            self.id
        )
        .fetch_one(db)
        .await
        .unwrap(); //TODO: fixme
        let amount_currently_registered = i64::from(amount_currently_registered.count);

        amount_currently_registered >= self.max_people
    }

    pub async fn pinned_days(db: &SqlitePool, amount_days_to_skip: i64) -> Vec<NaiveDate> {
        let query = format!(
            "SELECT DISTINCT day
FROM trip_details 
WHERE always_show=true AND day > datetime('now' , '+{} days')
ORDER BY day;",
            amount_days_to_skip
        );
        let days: Vec<String> = sqlx::query_scalar(&query).fetch_all(db).await.unwrap();
        days.into_iter()
            .map(|a| NaiveDate::parse_from_str(&a, "%Y-%m-%d").unwrap())
            .collect()
    }
    pub(crate) async fn user_is_rower(&self, db: &SqlitePool, user: &User) -> bool {
        //check if cox if planned_event
        let is_rower = sqlx::query!(
            "SELECT count(*) as amount
            FROM user_trip 
            WHERE trip_details_id = ? AND user_id = ?",
            self.id,
            user.id
        )
        .fetch_one(db)
        .await
        .unwrap();
        is_rower.amount > 0
    }

    async fn belongs_to_event(&self, db: &SqlitePool) -> bool {
        let amount = sqlx::query!(
            "SELECT count(*) as amount
            FROM planned_event
            WHERE trip_details_id = ?",
            self.id,
        )
        .fetch_one(db)
        .await
        .unwrap();
        amount.amount > 0
    }

    pub(crate) async fn user_allowed_to_change(&self, db: &SqlitePool, user: &User) -> bool {
        if self.belongs_to_event(db).await {
            user.has_role(db, "planned_event").await
        } else {
            self.user_is_cox(db, user).await != CoxAtTrip::No
        }
    }

    pub(crate) async fn user_is_cox(&self, db: &SqlitePool, user: &User) -> CoxAtTrip {
        //check if cox if planned_event
        let is_cox = sqlx::query!(
            "SELECT count(*) as amount
            FROM trip 
            WHERE planned_event_id = (
                SELECT id FROM planned_event WHERE trip_details_id = ?
            ) 
            AND cox_id = ?",
            self.id,
            user.id
        )
        .fetch_one(db)
        .await
        .unwrap();
        if is_cox.amount > 0 {
            return CoxAtTrip::Yes(Action::Helping);
        }

        //check if cox if own event
        let is_cox = sqlx::query!(
            "SELECT count(*) as amount
            FROM trip 
            WHERE trip_details_id = ? 
            AND cox_id = ?",
            self.id,
            user.id
        )
        .fetch_one(db)
        .await
        .unwrap();
        if is_cox.amount > 0 {
            return CoxAtTrip::Yes(Action::Own);
        }

        CoxAtTrip::No
    }
}

#[derive(PartialEq, Debug)]
pub(crate) enum CoxAtTrip {
    No,
    Yes(Action),
}

#[derive(PartialEq, Debug)]
pub(crate) enum Action {
    Helping,
    Own,
}

#[cfg(test)]
mod test {
    use crate::{model::tripdetails::TripDetailsToAdd, testdb};

    use super::TripDetails;
    use sqlx::SqlitePool;

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

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

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

        assert!(TripDetails::find_by_id(&pool, 1337).await.is_none());
    }

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

        assert_eq!(
            TripDetails::create(
                &pool,
                TripDetailsToAdd {
                    planned_starting_time: "10:00".into(),
                    max_people: 2,
                    day: "1970-01-01".into(),
                    notes: None,
                    allow_guests: false,
                    trip_type: None,
                    always_show: false
                }
            )
            .await,
            3,
        );
        assert_eq!(
            TripDetails::create(
                &pool,
                TripDetailsToAdd {
                    planned_starting_time: "10:00".into(),
                    max_people: 2,
                    day: "1970-01-01".into(),
                    notes: None,
                    allow_guests: false,
                    trip_type: None,
                    always_show: false
                }
            )
            .await,
            4,
        );
    }

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

        let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap();
        assert_eq!(trip_details.is_full(&pool).await, false);
    }

    #[sqlx::test]
    fn test_true_full() {
        //TODO: register user for trip_details = 1; check if is_full returns true
    }

    //TODO: add new tripdetails test with trip_type != None
}
