use assert_cmd::{assert::Assert, Command};

use sqlx::{migrate::Migrate, Connection, SqliteConnection};
use std::{
    env::temp_dir,
    fs::remove_file,
    path::{Path, PathBuf},
};

pub struct TestDatabase {
    file_path: PathBuf,
    migrations: String,
}

impl TestDatabase {
    pub fn new(name: &str, migrations: &str) -> Self {
        let migrations_path = Path::new("tests").join(migrations);
        let file_path = Path::new(&temp_dir()).join(format!("test-{}.db", name));
        let ret = Self {
            file_path,
            migrations: String::from(migrations_path.to_str().unwrap()),
        };
        Command::cargo_bin("cargo-sqlx")
            .unwrap()
            .args([
                "sqlx",
                "database",
                "create",
                "--database-url",
                &ret.connection_string(),
            ])
            .assert()
            .success();
        ret
    }

    pub fn connection_string(&self) -> String {
        format!("sqlite://{}", self.file_path.display())
    }

    pub fn run_migration(&self, revert: bool, version: Option<i64>, dry_run: bool) -> Assert {
        let ver = match version {
            Some(v) => v.to_string(),
            None => String::from(""),
        };
        Command::cargo_bin("cargo-sqlx")
            .unwrap()
            .args(
                [
                    vec![
                        "sqlx",
                        "migrate",
                        match revert {
                            true => "revert",
                            false => "run",
                        },
                        "--database-url",
                        &self.connection_string(),
                        "--source",
                        &self.migrations,
                    ],
                    match version {
                        Some(_) => vec!["--target-version", &ver],
                        None => vec![],
                    },
                    match dry_run {
                        true => vec!["--dry-run"],
                        false => vec![],
                    },
                ]
                .concat(),
            )
            .assert()
    }

    pub async fn applied_migrations(&self) -> Vec<i64> {
        let mut conn = SqliteConnection::connect(&self.connection_string())
            .await
            .unwrap();
        conn.list_applied_migrations()
            .await
            .unwrap()
            .iter()
            .map(|m| m.version)
            .collect()
    }
}

impl Drop for TestDatabase {
    fn drop(&mut self) {
        remove_file(&self.file_path).unwrap();
    }
}