use derive_more::{Deref, DerefMut};
use mysql_async::Pool;
use testcontainers_modules::testcontainers::ContainerAsync;

use crate::Error;

// This is a bit YOLO but is the quickest/cleanest way I can think of to a) get
// a connection per database and b) ensure `Drop` cleans it up.
#[derive(Deref, DerefMut)]
struct TestPool(
    #[deref]
    #[deref_mut]
    Pool,
    // Never read, only needed for `Drop`.
    #[allow(dead_code)] ContainerAsync<testcontainers_modules::mysql::Mysql>,
);

impl TestPool {
    async fn try_new() -> Result<TestPool, Error> {
        use testcontainers_modules::{mysql, testcontainers::runners::AsyncRunner};
        let mysql = mysql::Mysql::default().start().await?;
        let url = format!(
            "mysql://root@{}:{}/test",
            mysql.get_host().await?,
            mysql.get_host_port_ipv4(3306).await?
        );
        let pool = Pool::from_url(&url)?;
        Ok(TestPool(pool, mysql))
    }
}

#[tokio::test]
async fn test_mysql() -> Result<(), Error> {
    let pool = TestPool::try_new().await?;
    crate::load_data(&pool).await?;
    time_test::time_test!();
    let tables = vec!["employees", "departments"];
    let mut handles: Vec<tokio::task::JoinHandle<Result<(), crate::Error>>> = vec![];
    for table in tables {
        handles.push(tokio::spawn({
            let pool = pool.clone();
            let table = table.to_string();
            async move {
                crate::convert_data(&pool, &table).await?;
                Ok(())
            }
        }));
    }
    for handle in handles {
        let _ = handle.await?;
    }
    Ok(())
}
