use crate::connections::db_connection;
use crate::constants::FAMILY_INVITATION_CODE_LEN;
use crate::manual_schema::families_memberships;
use crate::models::{
    Family, FamilyID, FamilyMembership, Membership, NewFamily, NewMembership, UserID,
};
use crate::schema::{families, memberships};
use crate::services::{
    accommodations_list_service, couples_service, members_service, users_service,
};
use crate::utils::string_utils::rand_str;
use crate::utils::time_utils::time;
use diesel::prelude::*;

/// Create a new family, with an initial administrator
pub async fn create(name: &str, user_id: UserID) -> anyhow::Result<Family> {
    let family = db_connection::execute(|conn| {
        let res: Family = diesel::insert_into(families::table)
            .values(&NewFamily {
                name: name.trim(),
                invitation_code: rand_str(FAMILY_INVITATION_CODE_LEN),
                time_create: time() as i64,
            })
            .get_result(conn)?;

        Ok(res)
    })?;

    add_member(family.id(), user_id, true).await?;

    Ok(family)
}

/// Add a member to a family
pub async fn add_member(
    family_id: FamilyID,
    user_id: UserID,
    admin: bool,
) -> anyhow::Result<Membership> {
    db_connection::execute(|conn| {
        let res = diesel::insert_into(memberships::table)
            .values(&NewMembership {
                user_id: user_id.0,
                family_id: family_id.0,
                time_create: time() as i64,
                is_admin: admin,
            })
            .get_result(conn)?;

        Ok(res)
    })
}

/// Find a family by id
pub async fn get_by_id(id: FamilyID) -> anyhow::Result<Family> {
    db_connection::execute(|conn| {
        families::table
            .filter(families::dsl::id.eq(id.0))
            .first(conn)
    })
}

/// Find a family by invitation code
pub async fn get_by_invitation_code(code: &str) -> anyhow::Result<Family> {
    db_connection::execute(|conn| {
        families::table
            .filter(families::dsl::invitation_code.eq(code))
            .first(conn)
    })
}

/// Check if a given user is member of a family or not
pub async fn is_member(family_id: FamilyID, user_id: UserID) -> anyhow::Result<bool> {
    db_connection::execute(|conn| {
        memberships::table
            .filter(memberships::dsl::family_id.eq(family_id.0))
            .filter(memberships::dsl::user_id.eq(user_id.0))
            .count()
            .get_result(conn)
    })
    .map(|c: i64| c > 0)
}

/// Get the memberships of a user, with family info
pub async fn get_user_family_memberships(user_id: UserID) -> anyhow::Result<Vec<FamilyMembership>> {
    db_connection::execute(|conn| {
        families_memberships::table
            .filter(families_memberships::dsl::user_id.eq(user_id.0))
            .get_results(conn)
    })
}

/// Get the memberships of a user, without family info
pub async fn get_user_memberships(user_id: UserID) -> anyhow::Result<Vec<Membership>> {
    db_connection::execute(|conn| {
        memberships::table
            .filter(memberships::dsl::user_id.eq(user_id.0))
            .get_results(conn)
    })
}

/// Get information about a membership of a user
pub async fn get_membership(family_id: FamilyID, user_id: UserID) -> anyhow::Result<Membership> {
    db_connection::execute(|conn| {
        memberships::table
            .filter(memberships::dsl::user_id.eq(user_id.0))
            .filter(memberships::dsl::family_id.eq(family_id.0))
            .get_result(conn)
    })
}

/// Update a membership to a family
pub async fn update_membership(membership: &Membership) -> anyhow::Result<()> {
    db_connection::execute(|conn| {
        diesel::update(
            memberships::dsl::memberships.filter(
                memberships::dsl::user_id
                    .eq(membership.user_id().0)
                    .and(memberships::dsl::family_id.eq(membership.family_id().0)),
            ),
        )
        .set(memberships::dsl::is_admin.eq(membership.is_admin))
        .execute(conn)
    })?;

    Ok(())
}

#[derive(serde::Serialize)]
pub struct FamilyMember {
    #[serde(flatten)]
    pub membership: Membership,
    pub user_name: String,
    pub user_mail: String,
}

/// Get information about the users of a family
pub async fn get_memberships_of_family(family_id: FamilyID) -> anyhow::Result<Vec<FamilyMember>> {
    let memberships = db_connection::execute(|conn| {
        memberships::table
            .filter(memberships::dsl::family_id.eq(family_id.0))
            .get_results::<Membership>(conn)
    })?;

    let mut out = Vec::with_capacity(memberships.len());

    for m in memberships {
        let user = users_service::get_by_id(m.user_id()).await?;
        out.push(FamilyMember {
            user_name: user.name,
            user_mail: user.email,
            membership: m,
        })
    }

    Ok(out)
}

/// Get information about a membership of a user, joined with family information
pub async fn get_family_membership(
    family_id: FamilyID,
    user_id: UserID,
) -> anyhow::Result<FamilyMembership> {
    db_connection::execute(|conn| {
        families_memberships::table
            .filter(families_memberships::dsl::user_id.eq(user_id.0))
            .filter(families_memberships::dsl::family_id.eq(family_id.0))
            .get_result(conn)
    })
}

/// Update a family
pub async fn update_family(family: &Family) -> anyhow::Result<()> {
    db_connection::execute(|conn| {
        diesel::update(families::dsl::families.filter(families::dsl::id.eq(family.id().0)))
            .set((
                families::dsl::name.eq(family.name.clone()),
                families::dsl::invitation_code.eq(family.invitation_code.clone()),
                families::dsl::enable_genealogy.eq(family.enable_genealogy),
                families::dsl::enable_accommodations.eq(family.enable_accommodations),
                families::dsl::disable_couple_photos.eq(family.disable_couple_photos),
            ))
            .execute(conn)
    })?;

    Ok(())
}

/// Delete a family
pub async fn delete_family(family_id: FamilyID) -> anyhow::Result<()> {
    // Delete all family accommodations
    accommodations_list_service::delete_all_family(family_id).await?;

    // Delete all family couples
    couples_service::delete_all_family(family_id).await?;

    // Remove all family members
    members_service::delete_all_family(family_id).await?;

    // Remove all memberships
    db_connection::execute(|conn| {
        diesel::delete(
            memberships::dsl::memberships.filter(memberships::dsl::family_id.eq(family_id.0)),
        )
        .execute(conn)
    })?;

    // Remove the family itself
    db_connection::execute(|conn| {
        diesel::delete(families::dsl::families.filter(families::dsl::id.eq(family_id.0)))
            .execute(conn)
    })?;

    Ok(())
}

/// Remove a membership to a family
pub async fn remove_membership(family_id: FamilyID, user_id: UserID) -> anyhow::Result<()> {
    let family = get_family_membership(family_id, user_id).await?;

    if family.is_admin && family.count_admins == 1 {
        // We need to delete the whole family
        delete_family(family_id).await
    } else {
        // Remove the single membership
        db_connection::execute(|conn| {
            diesel::delete(
                memberships::dsl::memberships.filter(
                    memberships::dsl::user_id
                        .eq(user_id.0)
                        .and(memberships::dsl::family_id.eq(family_id.0)),
                ),
            )
            .execute(conn)
        })?;

        Ok(())
    }
}

/// Remove all memberships of user
pub async fn remove_all_user_membership(user_id: UserID) -> anyhow::Result<()> {
    for m in get_user_memberships(user_id).await? {
        remove_membership(m.family_id(), user_id).await?;
    }

    Ok(())
}
