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

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

#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct BoatDamage {
    pub id: i64,
    pub boat_id: i64,
    pub desc: String,
    pub user_id_created: i64,
    pub created_at: NaiveDateTime,
    pub user_id_fixed: Option<i64>,
    pub fixed_at: Option<NaiveDateTime>,
    pub user_id_verified: Option<i64>,
    pub verified_at: Option<NaiveDateTime>,
    pub lock_boat: bool,
}

#[derive(FromRow, Debug, Serialize, Deserialize)]
pub struct BoatDamageWithDetails {
    #[serde(flatten)]
    boat_damage: BoatDamage,
    user_created: User,
    user_fixed: Option<User>,
    user_verified: Option<User>,
    boat: Boat,
    verified: bool,
}

#[derive(Debug)]
pub struct BoatDamageToAdd<'r> {
    pub boat_id: i64,
    pub desc: &'r str,
    pub user_id_created: i32,
    pub lock_boat: bool,
}

#[derive(FromForm, Debug)]
pub struct BoatDamageFixed<'r> {
    pub desc: &'r str,
    pub user_id_fixed: i32,
}

#[derive(FromForm, Debug)]
pub struct BoatDamageVerified<'r> {
    pub desc: &'r str,
    pub user_id_verified: i32,
}

impl BoatDamage {
    pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option<Self> {
        sqlx::query_as!(
            Self,
            "SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat 
             FROM boat_damage
             WHERE id like ?",
            id
        )
        .fetch_one(db)
        .await
        .ok()
    }

    pub async fn all(db: &SqlitePool) -> Vec<BoatDamageWithDetails> {
        let boatdamages = sqlx::query_as!(
            BoatDamage,
            "
SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat 
FROM boat_damage
ORDER BY created_at DESC
        "
        )
        .fetch_all(db)
        .await
        .unwrap(); //TODO: fixme

        let mut res = Vec::new();
        for boat_damage in boatdamages {
            let user_fixed = match boat_damage.user_id_fixed {
                Some(id) => {
                    let user = User::find_by_id(db, id as i32).await;
                    Some(user.unwrap())
                }
                None => None,
            };
            let user_verified = match boat_damage.user_id_verified {
                Some(id) => {
                    let user = User::find_by_id(db, id as i32).await;
                    Some(user.unwrap())
                }
                None => None,
            };

            res.push(BoatDamageWithDetails {
                boat: Boat::find_by_id(db, boat_damage.boat_id as i32)
                    .await
                    .unwrap(),
                user_created: User::find_by_id(db, boat_damage.user_id_created as i32)
                    .await
                    .unwrap(),
                user_fixed,
                verified: user_verified.is_some(),
                user_verified,
                boat_damage,
            });
        }
        res
    }

    pub async fn create(db: &SqlitePool, boatdamage: BoatDamageToAdd<'_>) -> Result<(), String> {
        Log::create(db, format!("New boat damage: {boatdamage:?}")).await;

        sqlx::query!(
            "INSERT INTO boat_damage(boat_id, desc, user_id_created, lock_boat) VALUES (?,?,?, ?)",
            boatdamage.boat_id,
            boatdamage.desc,
            boatdamage.user_id_created,
            boatdamage.lock_boat
        )
        .execute(db)
        .await
        .map_err(|e| e.to_string())?;

        let technicals =
            User::all_with_role(db, &Role::find_by_name(db, "tech").await.unwrap()).await;
        for technical in technicals {
            if technical.id as i32 != boatdamage.user_id_created {
                Notification::create(
                    db,
                    &technical,
                    &format!(
                        "{} hat einen neuen Bootschaden für Boot '{}' angelegt: {}",
                        User::find_by_id(db, boatdamage.user_id_created)
                            .await
                            .unwrap()
                            .name,
                        Boat::find_by_id(db, boatdamage.boat_id as i32)
                            .await
                            .unwrap()
                            .name,
                        boatdamage.desc
                    ),
                    "Neuer Bootsschaden angelegt",
                    None,
                )
                .await;
            }
        }

        Notification::create(
            db,
            &User::find_by_id(db, boatdamage.user_id_created)
                .await
                .unwrap(),
            &format!(
                "Du hat einen neuen Bootschaden für Boot '{}' angelegt: {}",
                Boat::find_by_id(db, boatdamage.boat_id as i32)
                    .await
                    .unwrap()
                    .name,
                boatdamage.desc
            ),
            "Neuer Bootsschaden angelegt",
            None,
        )
        .await;

        Ok(())
    }

    pub async fn fixed(
        &self,
        db: &SqlitePool,
        boat_damage: BoatDamageFixed<'_>,
    ) -> Result<(), String> {
        Log::create(db, format!("Fixed boat damage: {boat_damage:?}")).await;

        let boat = Boat::find_by_id(db, self.boat_id as i32).await.unwrap();

        sqlx::query!(
            "UPDATE boat_damage SET desc=?, user_id_fixed=?, fixed_at=CURRENT_TIMESTAMP WHERE id=?",
            boat_damage.desc,
            boat_damage.user_id_fixed,
            self.id
        )
        .execute(db)
        .await
        .map_err(|e| e.to_string())?;

        let user = User::find_by_id(db, boat_damage.user_id_fixed)
            .await
            .unwrap();
        if user.has_role(db, "tech").await {
            return self
                .verified(
                    db,
                    BoatDamageVerified {
                        desc: boat_damage.desc,
                        user_id_verified: user.id as i32,
                    },
                )
                .await;
        }

        let technicals =
            User::all_with_role(db, &Role::find_by_name(db, "tech").await.unwrap()).await;
        for technical in technicals {
            if technical.id as i32 != boat_damage.user_id_fixed {
                Notification::create(
                    db,
                    &technical,
                    &format!(
                        "{} hat den Bootschaden '{}' beim Boot '{}' repariert. Könntest du das bei Gelegenheit verifizieren?",
                        User::find_by_id(db, boat_damage.user_id_fixed)
                            .await
                            .unwrap()
                            .name,
                        boat_damage.desc,
                       boat.name,
                    ),
                    "Bootsschaden repariert",
                    None,
                )
                .await;
            }
        }

        Notification::create(
            db,
            &User::find_by_id(db, boat_damage.user_id_fixed)
                .await
                .unwrap(),
            &format!(
                "Du hat den Bootschaden '{}' beim Boot '{}' repariert. Danke für deine Hilfe!",
                boat_damage.desc, boat.name,
            ),
            "Bootsschaden repariert",
            None,
        )
        .await;

        Ok(())
    }

    pub async fn verified(
        &self,
        db: &SqlitePool,
        boat: BoatDamageVerified<'_>,
    ) -> Result<(), String> {
        if let Some(verifier) = User::find_by_id(db, boat.user_id_verified).await {
            if !verifier.has_role(db, "tech").await {
                Log::create(db, format!("User {verifier:?} tried to verify boat {boat:?}. The user is no tech. Manually craftted request?")).await;
                return Err("You are not allowed to verify the boat!".into());
            }
        } else {
            Log::create(db, format!("Someone tried to verify the boat {boat:?} with user_id={} which does not exist. Manually craftted request?", boat.user_id_verified)).await;
            return Err("Could not find user".into());
        }

        Log::create(db, format!("Verified boat damage: {boat:?}")).await;

        sqlx::query!(
            "UPDATE boat_damage SET desc=?, user_id_verified=?, verified_at=CURRENT_TIMESTAMP WHERE id=?",
            boat.desc,
            boat.user_id_verified,
            self.id
        )
        .execute(db)
        .await.map_err(|e| e.to_string())?;
        Ok(())
    }
}
