extern crate chrono;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use chrono::NaiveDateTime;
#[cfg(test)]
use diesel::debug_query;
use diesel::insert_into;
#[cfg(test)]
use diesel::mysql::Mysql;
use diesel::prelude::*;
use std::error::Error;

mod schema {
    table! {
        users {
            id -> Integer,
            name -> Text,
            hair_color -> Nullable<Text>,
            created_at -> Timestamp,
            updated_at -> Timestamp,
        }
    }
}

use schema::users;

#[derive(Deserialize, Insertable)]
#[table_name = "users"]
pub struct UserForm<'a> {
    name: &'a str,
    hair_color: Option<&'a str>,
}

#[derive(Queryable, PartialEq, Debug)]
struct User {
    id: i32,
    name: String,
    hair_color: Option<String>,
    created_at: NaiveDateTime,
    updated_at: NaiveDateTime,
}

pub fn insert_default_values(conn: &MysqlConnection) -> QueryResult<usize> {
    use schema::users::dsl::*;

    insert_into(users).default_values().execute(conn)
}

#[test]
fn examine_sql_from_insert_default_values() {
    use schema::users::dsl::*;

    let query = insert_into(users).default_values();
    let sql = "INSERT INTO `users` () VALUES () -- binds: []";
    assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}

pub fn insert_single_column(conn: &MysqlConnection) -> QueryResult<usize> {
    use schema::users::dsl::*;

    insert_into(users).values(name.eq("Sean")).execute(conn)
}

#[test]
fn examine_sql_from_insert_single_column() {
    use schema::users::dsl::*;

    let query = insert_into(users).values(name.eq("Sean"));
    let sql = "INSERT INTO `users` (`name`) VALUES (?) \
               -- binds: [\"Sean\"]";
    assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}

pub fn insert_multiple_columns(conn: &MysqlConnection) -> QueryResult<usize> {
    use schema::users::dsl::*;

    insert_into(users)
        .values((name.eq("Tess"), hair_color.eq("Brown")))
        .execute(conn)
}

#[test]
fn examine_sql_from_insert_multiple_columns() {
    use schema::users::dsl::*;

    let query = insert_into(users).values((name.eq("Tess"), hair_color.eq("Brown")));
    let sql = "INSERT INTO `users` (`name`, `hair_color`) VALUES (?, ?) \
               -- binds: [\"Tess\", \"Brown\"]";
    assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}

pub fn insert_insertable_struct(conn: &MysqlConnection) -> Result<(), Box<dyn Error>> {
    use schema::users::dsl::*;

    let json = r#"{ "name": "Sean", "hair_color": "Black" }"#;
    let user_form = serde_json::from_str::<UserForm>(json)?;

    insert_into(users).values(&user_form).execute(conn)?;

    Ok(())
}

#[test]
fn examine_sql_from_insertable_struct() {
    use schema::users::dsl::*;

    let json = r#"{ "name": "Sean", "hair_color": "Black" }"#;
    let user_form = serde_json::from_str::<UserForm>(json).unwrap();
    let query = insert_into(users).values(&user_form);
    let sql = "INSERT INTO `users` (`name`, `hair_color`) VALUES (?, ?) \
               -- binds: [\"Sean\", \"Black\"]";
    assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}

pub fn insert_insertable_struct_option(conn: &MysqlConnection) -> Result<(), Box<dyn Error>> {
    use schema::users::dsl::*;

    let json = r#"{ "name": "Ruby", "hair_color": null }"#;
    let user_form = serde_json::from_str::<UserForm>(json)?;

    insert_into(users).values(&user_form).execute(conn)?;

    Ok(())
}


#[test]
fn examine_sql_from_insertable_struct_batch() {
    use schema::users::dsl::*;

    let json = r#"[
        { "name": "Sean", "hair_color": "Black" },
        { "name": "Tess", "hair_color": "Brown" }
    ]"#;
    let user_form = serde_json::from_str::<Vec<UserForm>>(json).unwrap();
    let query = insert_into(users).values(&user_form);
    let sql = "INSERT INTO `users` (`name`, `hair_color`) \
               VALUES (?, ?), (?, ?) \
               -- binds: [\"Sean\", \"Black\", \"Tess\", \"Brown\"]";
    assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}

#[test]
fn insert_get_results_batch() {
    use diesel::result::Error;

    let conn = establish_connection();
    conn.test_transaction::<_, Error, _>(|| {
        use diesel::select;
        use schema::users::dsl::*;

        let now = select(diesel::dsl::now).get_result::<NaiveDateTime>(&conn)?;

        let inserted_users = conn.transaction::<_, Error, _>(|| {
            let inserted_count = insert_into(users)
                .values(&vec![
                    (id.eq(1), name.eq("Sean")),
                    (id.eq(2), name.eq("Tess")),
                ])
                .execute(&conn)?;

            Ok(users
                .order(id.desc())
                .limit(inserted_count as i64)
                .load(&conn)?
                .into_iter()
                .rev()
                .collect::<Vec<_>>())
        })?;

        let expected_users = vec![
            User {
                id: 1,
                name: "Sean".into(),
                hair_color: None,
                created_at: now,
                updated_at: now,
            },
            User {
                id: 2,
                name: "Tess".into(),
                hair_color: None,
                created_at: now,
                updated_at: now,
            },
        ];
        assert_eq!(expected_users, inserted_users);

        Ok(())
    });
}

#[test]
fn examine_sql_from_insert_get_results_batch() {
    use schema::users::dsl::*;

    let values = vec![(id.eq(1), name.eq("Sean")), (id.eq(2), name.eq("Tess"))];
    let insert_query = insert_into(users).values(&values);
    let insert_sql = "INSERT INTO `users` (`id`, `name`) VALUES (?, ?), (?, ?) \
                      -- binds: [1, \"Sean\", 2, \"Tess\"]";
    assert_eq!(
        insert_sql,
        debug_query::<Mysql, _>(&insert_query).to_string()
    );
    let load_query = users.order(id.desc());
    let load_sql = "SELECT `users`.`id`, `users`.`name`, \
                    `users`.`hair_color`, `users`.`created_at`, \
                    `users`.`updated_at` \
                    FROM `users` \
                    ORDER BY `users`.`id` DESC \
                    -- binds: []";
    assert_eq!(load_sql, debug_query::<Mysql, _>(&load_query).to_string());
}

#[test]
fn insert_get_result() {
    use diesel::result::Error;

    let conn = establish_connection();
    conn.test_transaction::<_, Error, _>(|| {
        use diesel::select;
        use schema::users::dsl::*;

        let now = select(diesel::dsl::now).get_result::<NaiveDateTime>(&conn)?;

        let inserted_user = conn.transaction::<_, Error, _>(|| {
            insert_into(users)
                .values((id.eq(3), name.eq("Ruby")))
                .execute(&conn)?;

            users.order(id.desc()).first(&conn)
        })?;

        let expected_user = User {
            id: 3,
            name: "Ruby".into(),
            hair_color: None,
            created_at: now,
            updated_at: now,
        };
        assert_eq!(expected_user, inserted_user);

        Ok(())
    });
}

#[test]
fn examine_sql_from_insert_get_result() {
    use schema::users::dsl::*;

    let insert_query = insert_into(users).values((id.eq(3), name.eq("Ruby")));
    let insert_sql = "INSERT INTO `users` (`id`, `name`) VALUES (?, ?) -- binds: [3, \"Ruby\"]";
    assert_eq!(
        insert_sql,
        debug_query::<Mysql, _>(&insert_query).to_string()
    );
    let load_query = users.order(id.desc());
    let load_sql = "SELECT `users`.`id`, `users`.`name`, \
                    `users`.`hair_color`, `users`.`created_at`, \
                    `users`.`updated_at` \
                    FROM `users` \
                    ORDER BY `users`.`id` DESC \
                    -- binds: []";
    assert_eq!(load_sql, debug_query::<Mysql, _>(&load_query).to_string());
}

pub fn explicit_returning(conn: &MysqlConnection) -> QueryResult<i32> {
    use diesel::result::Error;
    use schema::users::dsl::*;

    conn.transaction::<_, Error, _>(|| {
        insert_into(users).values(name.eq("Ruby")).execute(conn)?;

        users.select(id).order(id.desc()).first(conn)
    })
}

#[test]
fn examine_sql_from_explicit_returning() {
    use schema::users::dsl::*;

    let insert_query = insert_into(users).values(name.eq("Ruby"));
    let insert_sql = "INSERT INTO `users` (`name`) VALUES (?) -- binds: [\"Ruby\"]";
    assert_eq!(
        insert_sql,
        debug_query::<Mysql, _>(&insert_query).to_string()
    );
    let load_query = users.select(id).order(id.desc());
    let load_sql = "SELECT `users`.`id` FROM `users` ORDER BY `users`.`id` DESC -- binds: []";
    assert_eq!(load_sql, debug_query::<Mysql, _>(&load_query).to_string());
}

#[cfg(test)]
fn establish_connection() -> MysqlConnection {
    let url = ::std::env::var("DATABASE_URL").unwrap();
    MysqlConnection::establish(&url).unwrap()
}
