use std::collections::HashMap;

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

use super::log::Log;
use super::notification::Notification;
use super::role::Role;

#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct BoatReservation {
    pub id: i64,
    pub boat_id: i64,
    pub start_date: NaiveDate,
    pub end_date: NaiveDate,
    pub time_desc: String,
    pub usage: String,
    pub user_id_applicant: i64,
    pub user_id_confirmation: Option<i64>,
    pub created_at: NaiveDateTime,
}

#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct BoatReservationWithDetails {
    #[serde(flatten)]
    reservation: BoatReservation,
    boat: Boat,
    user_applicant: User,
    user_confirmation: Option<User>,
}

#[derive(Debug)]
pub struct BoatReservationToAdd<'r> {
    pub boat: &'r Boat,
    pub start_date: NaiveDate,
    pub end_date: NaiveDate,
    pub time_desc: &'r str,
    pub usage: &'r str,
    pub user_applicant: &'r User,
}

impl BoatReservation {
    pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
        sqlx::query_as!(
            Self,
            "SELECT id, boat_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at
             FROM boat_reservation
             WHERE id like ?",
            id
        )
        .fetch_one(db)
        .await
        .ok()
    }

    pub async fn all_future(db: &SqlitePool) -> Vec<BoatReservationWithDetails> {
        let boatreservations = sqlx::query_as!(
            Self,
            "
SELECT id, boat_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at
FROM boat_reservation
WHERE end_date >= CURRENT_DATE ORDER BY end_date 
        "
        )
        .fetch_all(db)
        .await
        .unwrap(); //TODO: fixme

        let mut res = Vec::new();
        for reservation in boatreservations {
            let user_confirmation = match reservation.user_id_confirmation {
                Some(id) => {
                    let user = User::find_by_id(db, id as i32).await;
                    Some(user.unwrap())
                }
                None => None,
            };
            let user_applicant = User::find_by_id(db, reservation.user_id_applicant as i32)
                .await
                .unwrap();
            let boat = Boat::find_by_id(db, reservation.boat_id as i32)
                .await
                .unwrap();

            res.push(BoatReservationWithDetails {
                reservation,
                boat,
                user_applicant,
                user_confirmation,
            });
        }
        res
    }
    pub async fn all_future_with_groups(
        db: &SqlitePool,
    ) -> HashMap<String, Vec<BoatReservationWithDetails>> {
        let mut grouped_reservations: HashMap<String, Vec<BoatReservationWithDetails>> =
            HashMap::new();

        let reservations = Self::all_future(db).await;
        for reservation in reservations {
            let key = format!(
                "{}-{}-{}-{}-{}",
                reservation.reservation.start_date,
                reservation.reservation.end_date,
                reservation.reservation.time_desc,
                reservation.reservation.usage,
                reservation.user_applicant.name
            );

            grouped_reservations
                .entry(key)
                .or_insert_with(Vec::new)
                .push(reservation);
        }

        grouped_reservations
    }

    pub async fn create(
        db: &SqlitePool,
        boatreservation: BoatReservationToAdd<'_>,
    ) -> Result<(), String> {
        if Self::boat_reserved_between_dates(
            db,
            boatreservation.boat,
            &boatreservation.start_date,
            &boatreservation.end_date,
        )
        .await
        {
            return Err("Boot in diesem Zeitraum bereits reserviert.".into());
        }

        Log::create(db, format!("New boat reservation: {boatreservation:?}")).await;

        sqlx::query!(
            "INSERT INTO boat_reservation(boat_id, start_date, end_date, time_desc, usage, user_id_applicant) VALUES (?,?,?,?,?,?)",
            boatreservation.boat.id,
            boatreservation.start_date,
            boatreservation.end_date,
            boatreservation.time_desc,
            boatreservation.usage,
            boatreservation.user_applicant.id,
        )
        .execute(db)
        .await
        .map_err(|e| e.to_string())?;

        let board =
            User::all_with_role(db, &Role::find_by_name(db, "Vorstand").await.unwrap()).await;
        for user in board {
            let date = if boatreservation.start_date == boatreservation.end_date {
                format!("am {}", boatreservation.start_date)
            } else {
                format!(
                    "von {} bis {}",
                    boatreservation.start_date, boatreservation.end_date
                )
            };

            Notification::create(
                    db,
                    &user,
                    &format!(
                        "{} hat eine neue Bootsreservierung für Boot '{}' {} angelegt. Zeit: {}; Zweck: {}",
                        boatreservation.user_applicant.name,
                       boatreservation.boat.name,
                       date,
                        boatreservation.time_desc,
                        boatreservation.usage
                    ),
                    "Neue Bootsreservierung",
                    None,
                )
                .await;
        }

        Ok(())
    }

    pub async fn boat_reserved_between_dates(
        db: &SqlitePool,
        boat: &Boat,
        start_date: &NaiveDate,
        end_date: &NaiveDate,
    ) -> bool {
        sqlx::query!(
            "SELECT COUNT(*) AS reservation_count
FROM boat_reservation
WHERE boat_id = ? 
AND start_date <= ? AND end_date >= ?;",
            boat.id,
            end_date,
            start_date
        )
        .fetch_one(db)
        .await
        .unwrap()
        .reservation_count
            > 0
    }

    pub async fn delete(&self, db: &SqlitePool) {
        sqlx::query!("DELETE FROM boat_reservation WHERE id=?", self.id)
            .execute(db)
            .await
            .unwrap(); //Okay, because we can only create a Boat of a valid id
    }
}