use common::entities::LineItem;
use petro::entity::Entity as _;
use petro::entity::EntityId;
use petro::entity::EntityRef;
use petro::entity::SubentityList;
use petro::expr;
use petro::op;
use petro::query::delete_entity;
use petro::query::delete_entity_bulk;
use petro::query::insert_entity;
use petro::query::insert_entity_bulk;
use petro::query::update_entity;
use petro::query::update_entity_bulk;
use petro::query::OrderDirection;
use petro::query::QueryBuilder;
use rust_decimal_macros::dec;

mod common;
use common::db::get_db_client;
use common::entities::Customer;
use common::entities::Order;

#[tokio::test]
async fn it_passes() {
    let mut client = get_db_client().await;
    let tx = client.transaction().await.unwrap();

    let order = &Order::alias("order_");
    let customer = &Customer::alias("customer");

    let mut qb = QueryBuilder::from(order);
    qb.inner_join_on_id(customer, expr!(order.c.customer_id))
        .where_cond(op!(order.c.order_number, "=", ["001"]))
        .order_by(vec![(expr!(customer.c.id), OrderDirection::Asc)]);

    let (o, _c) = qb.fetch_one(&tx, &(order, customer)).await.unwrap();

    assert_eq!(&o.order_number, "001");

    tx.rollback().await.unwrap(); // Rollback to keep the DB in the same state
}

#[tokio::test]
async fn it_passes_too() {
    let mut client = get_db_client().await;
    let tx = client.transaction().await.unwrap();

    let orig_line_items = SubentityList::Loaded(vec![
        LineItem {
            id: EntityId::from("L1"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 1".to_string(),
            line_total: dec!(100.00),
        },
        LineItem {
            id: EntityId::from("L2"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 2".to_string(),
            line_total: dec!(200.00),
        },
        LineItem {
            id: EntityId::from("L3"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 3".to_string(),
            line_total: dec!(300.00),
        },
        LineItem {
            id: EntityId::from("L4"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 4".to_string(),
            line_total: dec!(400.00),
        },
    ]);

    let orig_order = Order {
        id: EntityId::from("O3"),
        order_number: "003".to_string(),
        order_date: None,
        customer: EntityRef::from(&EntityId::from("C1")),
        total_amount: dec!(1000.00),
        line_items: orig_line_items,
    };

    insert_entity(&tx, &orig_order).await.unwrap();
    insert_entity_bulk(&tx, &orig_order.line_items.to_vec())
        .await
        .unwrap();

    let order = &Order::alias("order_");

    let id = EntityId::from("O3");
    let mut qb = QueryBuilder::from(order);
    qb.where_id(order, &id);

    let o = qb.fetch_one(&tx, &order).await.unwrap();

    assert_eq!(&o.order_number, "003");

    let item_t = &LineItem::alias("item_t");
    let mut qb = QueryBuilder::from(item_t);
    qb.where_cond(op!(item_t.c.order_id, "=", &id));
    let items = qb.fetch_unordered(&tx, &item_t, None, 0).await.unwrap();

    assert_eq!(items.len(), 4);

    let mut upd_order = orig_order.clone();
    upd_order.order_number = "004".to_string();
    upd_order.line_items = SubentityList::Loaded(vec![
        LineItem {
            id: EntityId::from("L1"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 1".to_string(),
            line_total: dec!(100.00),
        },
        LineItem {
            id: EntityId::from("L2"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 2".to_string(),
            line_total: dec!(300.00),
        },
        LineItem {
            id: EntityId::from("L5"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 5".to_string(),
            line_total: dec!(200.00),
        },
        LineItem {
            id: EntityId::from("L6"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 6".to_string(),
            line_total: dec!(200.00),
        },
        LineItem {
            id: EntityId::from("L7"),
            order: EntityRef::from(&EntityId::from("O3")),
            description: "Item 7".to_string(),
            line_total: dec!(200.00),
        },
    ]);

    update_entity(&tx, &orig_order, &upd_order).await.unwrap();
    update_entity_bulk(
        &tx,
        &orig_order.line_items.to_vec(),
        &upd_order.line_items.to_vec(),
    )
    .await
    .unwrap();

    let mut qb = QueryBuilder::from(order);
    qb.where_id(order, &id);
    let o = qb.fetch_one(&tx, &order).await.unwrap();
    assert_eq!(&o.order_number, "004");

    let item_t = &LineItem::alias("item_t");
    let mut qb = QueryBuilder::from(item_t);
    qb.where_cond(op!(item_t.c.order_id, "=", &id));
    let items = qb.fetch_unordered(&tx, &item_t, None, 0).await.unwrap();
    assert_eq!(items.len(), 5);

    delete_entity_bulk(&tx, &upd_order.line_items.to_vec())
        .await
        .unwrap();
    delete_entity(&tx, &upd_order).await.unwrap();

    let mut qb = QueryBuilder::from(order);
    qb.where_id(order, &id);
    let o = qb.fetch_optional(&tx, &order).await.unwrap();
    assert!(o.is_none());

    let item_t = &LineItem::alias("item_t");
    let mut qb = QueryBuilder::from(item_t);
    qb.where_cond(op!(item_t.c.order_id, "=", &id));
    let items = qb.fetch_unordered(&tx, &item_t, None, 0).await.unwrap();
    assert_eq!(items.len(), 0);

    tx.rollback().await.unwrap(); // Rollback to keep the DB in the same state
}

// TODO: Test query building without actually fetching (or do this in unit tests?)
// TODO: Test each of the possible joins and conditions
// TODO: Test mutations (insert, update, delete + versioned + bulk)
