use aws_config::Region;
use aws_credential_types::Credentials;
use aws_sdk_s3::Client;
use aws_sdk_s3::primitives::ByteStream;
use rsql_driver::Error::IoError;
use rsql_driver::{Driver, DriverManager, Result, Value};
use std::env;
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
use std::sync::Arc;
use testcontainers_modules::testcontainers::ContainerAsync;
use testcontainers_modules::testcontainers::core::Host;
use testcontainers_modules::testcontainers::core::logs::LogFrame;
use testcontainers_modules::testcontainers::runners::AsyncRunner;
use testcontainers_modules::{localstack::LocalStackPro, testcontainers::ImageExt};
use tracing::info;
use tracing_subscriber::EnvFilter;

static HOST: &str = "s3.localhost.localstack.cloud";
static BUCKET: &str = "test-bucket";
static ACCESS_KEY_ID: &str = "test";
static SECRET_ACCESS_KEY: &str = "test";
static REGION: &str = "us-east-1";

#[tokio::test(flavor = "multi_thread")]
async fn test_s3_driver_local_stack() -> Result<()> {
    if env::var("CI").unwrap_or_default() == "true" && env::consts::OS != "linux" {
        eprintln!("Skipping CI test on non-linux platform");
        return Ok(());
    }

    DriverManager::add(Arc::new(rsql_driver_csv::Driver))?;
    let subscriber = tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::new("trace")
                .add_directive("aws_config=trace".parse().expect("Invalid directive"))
                .add_directive("aws_sdk_s3=trace".parse().expect("Invalid directive")),
        )
        .with_test_writer()
        .compact()
        .finish();
    let _guard = tracing::subscriber::set_default(subscriber);

    let host = Host::Addr(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
    let container = LocalStackPro::with_auth_token(Option::<&str>::None)
        .with_env_var("SERVICES", "s3")
        .with_host(HOST, host)
        .with_log_consumer(|frame: &LogFrame| {
            let mut msg = std::str::from_utf8(frame.bytes()).expect("Failed to parse log message");
            if msg.ends_with('\n') {
                msg = &msg[..msg.len() - 1];
            }
            info!("{msg}");
        })
        .start()
        .await
        .map_err(|error| IoError(format!("{error:?}")))?;
    let database_url = upload_test_file(&container).await?;

    let driver = rsql_driver_s3::Driver;
    let mut connection = driver.connect(database_url.as_str()).await?;

    let mut query_result = connection
        .query("SELECT id, name FROM users ORDER BY id", &[])
        .await?;

    assert_eq!(query_result.columns(), vec!["id", "name"]);
    assert_eq!(
        query_result.next().await.cloned(),
        Some(vec![Value::I64(1), Value::String("John Doe".to_string())])
    );
    assert_eq!(
        query_result.next().await.cloned(),
        Some(vec![Value::I64(2), Value::String("Jane Smith".to_string())])
    );
    assert!(query_result.next().await.is_none());

    connection.close().await?;
    Ok(())
}

async fn upload_test_file(container: &ContainerAsync<LocalStackPro>) -> Result<String> {
    let port = container
        .get_host_port_ipv4(4566)
        .await
        .map_err(|error| IoError(format!("{error:?}")))?;
    let file_name = "users.csv";
    let endpoint_url = format!("http://{HOST}:{port}");
    let credentials = Credentials::from_keys(ACCESS_KEY_ID, SECRET_ACCESS_KEY, None);

    let config = aws_sdk_s3::config::Builder::default()
        .region(Region::new(REGION))
        .credentials_provider(credentials)
        .endpoint_url(&endpoint_url)
        .build();
    let client = Client::from_conf(config);

    client
        .create_bucket()
        .bucket(BUCKET)
        .send()
        .await
        .map_err(|error| IoError(format!("{error:?}")))?;

    let crate_directory = env!("CARGO_MANIFEST_DIR");
    let file_path = PathBuf::from(crate_directory)
        .join("..")
        .join("..")
        .join("datasets")
        .join(file_name);
    let byte_stream = ByteStream::from_path(file_path)
        .await
        .map_err(|error| IoError(format!("{error:?}")))?;

    client
        .put_object()
        .bucket(BUCKET)
        .key(file_name)
        .body(byte_stream)
        .send()
        .await
        .map_err(|error| IoError(format!("{error:?}")))?;

    let database_url = format!(
        "s3://{ACCESS_KEY_ID}:{SECRET_ACCESS_KEY}@{HOST}:{port}/{BUCKET}/{file_name}?region={REGION}&scheme=http",
    );
    Ok(database_url)
}
