use crate::schema::{build_inputs, queue};
use aliases::*;
use chrono::NaiveDateTime;
use diesel::dsl::exists;
use diesel::{
    ExpressionMethods, NullableExpressionMethods, QueryDsl, QueryResult, RunQueryDsl,
    SqliteConnection, SqliteExpressionMethods, delete, select, update,
};
use rebuilderd_common::errors::Error;

mod aliases {
    diesel::alias!(crate::schema::build_inputs as b1: BuildInputsAlias);
}

#[diesel::dsl::auto_type]
pub fn build_input_friends(id: i32) -> _ {
    build_inputs::table
        .select(build_inputs::id)
        .filter(
            build_inputs::url.eq_any(
                b1.filter(b1.field(build_inputs::id).is(id))
                    .select(b1.field(build_inputs::url)),
            ),
        )
        .filter(
            build_inputs::backend.eq_any(
                b1.filter(b1.field(build_inputs::id).is(id))
                    .select(b1.field(build_inputs::backend)),
            ),
        )
        .filter(
            build_inputs::architecture.eq_any(
                b1.filter(b1.field(build_inputs::id).is(id))
                    .select(b1.field(build_inputs::architecture)),
            ),
        )
}

pub fn get_build_input_friends(
    connection: &mut SqliteConnection,
    id: i32,
) -> QueryResult<Vec<i32>> {
    build_input_friends(id).load::<i32>(connection)
}

/// Set `next_retry` of the build_input to NULL, and remove any related item
/// from the build queue
pub fn mark_build_input_friends_as_non_retriable(
    connection: &mut SqliteConnection,
    id: i32,
) -> QueryResult<()> {
    let friends = get_build_input_friends(connection, id)?;

    // null out the next retry to mark the package and its friends as non-retried
    update(build_inputs::table)
        .filter(build_inputs::id.eq_any(&friends))
        .set(build_inputs::next_retry.eq(None::<NaiveDateTime>))
        .execute(connection)?;

    // drop any enqueued jobs for the build input and its friends
    delete(queue::table)
        .filter(queue::build_input_id.eq_any(&friends))
        .execute(connection)?;

    Ok(())
}

pub fn has_queued_friend(conn: &mut SqliteConnection, build_input_id: i32) -> Result<bool, Error> {
    let has_queued_friend = select(exists(
        queue::table
            .filter(queue::build_input_id.eq_any(build_input_friends(build_input_id)))
            .select(queue::id),
    ))
    .get_result::<bool>(conn)
    .map_err(Error::from)?;

    Ok(has_queued_friend)
}

pub fn get_largest_retry_count_among_friends(
    connection: &mut SqliteConnection,
    id: i32,
) -> QueryResult<i32> {
    let friends = get_build_input_friends(connection, id)?;
    let max_retry_count = build_inputs::table
        .filter(build_inputs::id.eq_any(&friends))
        .select(diesel::dsl::max(build_inputs::retries).assume_not_null())
        .get_result::<i32>(connection)?;

    Ok(max_retry_count)
}
