Documentation
use async_graphql::{
    connection::{Connection, Edge, PageInfo},
    OutputType,
};
use sqlx::{postgres::PgRow, Executor, FromRow, Postgres};

use crate::{
    cursor::Cursor,
    driver::{Driver, PushPrql},
    from::from,
    page::{select_page_info, select_page_items, Pagination, TotalCount},
    row::{upsert, Row},
    table::Table,
    Sorting,
};

pub async fn save_one<'c, E, R>(executor: E, row: R) -> sqlx::Result<()>
where
    E: Executor<'c, Database = Postgres>,
    R: Row + Table,
{
    let mut driver = Driver::new();
    upsert(row).push_to_driver(&mut driver);
    driver.execute_without_compilation(executor).await?;
    Ok(())
}

pub async fn load_one<'c, E, F, R>(executor: E, filter: F) -> sqlx::Result<Option<R>>
where
    E: Executor<'c, Database = Postgres>,
    F: PushPrql,
    for<'r> R: FromRow<'r, PgRow> + Table,
{
    let mut driver = Driver::new();

    from(R::table_name())
        .filter(filter)
        .take(1)
        .push_to_driver(&mut driver);

    driver
        .fetch_optional(executor)
        .await
        .and_then(|row| row.as_ref().map(R::from_row).transpose())
}

pub async fn load_page<'c, E, F, S, R>(
    executor: E,
    filter: F,
    sort: S,
    pagination: Pagination,
) -> sqlx::Result<Connection<String, R, TotalCount>>
where
    E: Copy + Executor<'c, Database = Postgres>,
    F: PushPrql,
    S: PushPrql + Sorting,
    for<'r> R: FromRow<'r, PgRow> + OutputType + Table,
{
    use sqlx::Row;

    let cursor = pagination.cursor;
    let subquery = from(R::table_name()).filter(filter);
    let subquery = subquery.sort(sort);

    let mut driver = Driver::new();
    select_page_items(&subquery, pagination).push_to_driver(&mut driver);

    let rows = driver.fetch_all(executor).await?;
    let edges = rows
        .into_iter()
        .map(|row| {
            Ok(Edge::new(
                Cursor::infer(row.try_get_raw("cursor")?)?,
                R::from_row(&row)?,
            ))
        })
        .collect::<sqlx::Result<Vec<_>>>()?;

    let start = edges
        .first()
        .map(|edge| edge.cursor.clone())
        .unwrap_or(Cursor::encode(&cursor.min()));
    let end = edges
        .last()
        .map(|edge| edge.cursor.clone())
        .unwrap_or(Cursor::encode(&cursor.max()));

    let mut driver = Driver::new();
    select_page_info(subquery, cursor, start.clone(), end.clone()).push_to_driver(&mut driver);
    let row = driver.fetch_one(executor).await?;
    let page_info = PageInfo {
        has_next_page: row.try_get("has_next_page")?,
        has_previous_page: row.try_get("has_prev_page")?,
        start_cursor: Some(start),
        end_cursor: Some(end),
    };
    let total_count = TotalCount {
        total_count: row.try_get("total_count")?,
    };

    let mut conn = Connection::with_additional_fields(
        page_info.has_previous_page,
        page_info.has_next_page,
        total_count,
    );
    conn.edges = edges;
    Ok(conn)
}