use crate::prelude::influxdb2::Client;
use crate::prelude::rand::{self, distributions::Alphanumeric, Rng};
use crate::prelude::tokio::{
    net::TcpStream,
    process::Command as AsyncCommand,
    time::{sleep, Instant},
};
use std::process::Command as SyncCommand;
use std::time::Duration;
use tracing::{info, trace};

/// on drop, the mysql docker container will be shut down
#[derive(Debug)]
pub struct TestInflux {
    pub host: String,
    pub org: String,
    pub token: String,
    pub bucket: String,
    pub container_name: String,
    pub client: Client,
}

impl TestInflux {
    pub async fn spawn() -> Self {
        let random: String = rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(8)
            .map(char::from)
            .collect();

        let container_name = format!("web3-proxy-test-influx-{}", random);

        info!(%container_name);

        // docker run -d -p 8086:8086 \
        // --name influxdb2 \
        // -v $PWD/data:/var/lib/influxdb2 \
        // -v $PWD/config:/etc/influxdb2 \
        // -e DOCKER_INFLUXDB_INIT_MODE=setup \
        // -e DOCKER_INFLUXDB_INIT_USERNAME=root \
        // -e DOCKER_INFLUXDB_INIT_PASSWORD=secret-password \
        // -e DOCKER_INFLUXDB_INIT_ORG=my-init-org \
        // -e DOCKER_INFLUXDB_INIT_BUCKET=my-init-bucket \
        // -e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=secret-token \

        let username = "dev_web3_proxy";
        let password = "dev_web3_proxy";
        let org = "dev_org";
        let init_bucket = "dev_web3_proxy";
        let admin_token = "dev_web3_proxy_auth_token";

        let cmd = AsyncCommand::new("docker")
            .args([
                "run",
                "--name",
                &container_name,
                "--rm",
                "-d",
                "-e",
                "DOCKER_INFLUXDB_INIT_MODE=setup",
                "-e",
                &format!("DOCKER_INFLUXDB_INIT_USERNAME={}", username),
                "-e",
                &format!("DOCKER_INFLUXDB_INIT_PASSWORD={}", password),
                "-e",
                &format!("DOCKER_INFLUXDB_INIT_ORG={}", org),
                "-e",
                &format!("DOCKER_INFLUXDB_INIT_BUCKET={}", init_bucket),
                "-e",
                &format!("DOCKER_INFLUXDB_INIT_ADMIN_TOKEN={}", admin_token),
                "-p",
                "0:8086",
                "influxdb:2.6.1-alpine",
            ])
            .output()
            .await
            .expect("failed to start influx");

        // original port 18086
        info!("Creation command is: {:?}", cmd);

        // give the db a second to start
        // TODO: wait until docker says it is healthy
        sleep(Duration::from_secs(1)).await;

        let docker_inspect_output = AsyncCommand::new("docker")
            .args(["inspect", &container_name])
            .output()
            .await
            .unwrap();

        info!(?docker_inspect_output);

        let docker_inspect_json = String::from_utf8(docker_inspect_output.stdout).unwrap();

        info!(%docker_inspect_json);

        let docker_inspect_json: serde_json::Value =
            serde_json::from_str(&docker_inspect_json).unwrap();

        let influx_ports = docker_inspect_json
            .get(0)
            .unwrap()
            .get("NetworkSettings")
            .unwrap()
            .get("Ports")
            .unwrap()
            .get("8086/tcp")
            .unwrap()
            .get(0)
            .unwrap();

        trace!(?influx_ports);

        let influx_port: u64 = influx_ports
            .get("HostPort")
            .expect("unable to determine influx port")
            .as_str()
            .unwrap()
            .parse()
            .unwrap();

        let influx_ip = influx_ports
            .get("HostIp")
            .and_then(|x| x.as_str())
            .expect("unable to determine influx ip");

        // let host = "http://localhost:8086";
        let influx_host = format!("http://{}:{}", influx_ip, influx_port);
        info!(%influx_host);

        // Create the client ...
        let influxdb_client = influxdb2::Client::new(influx_host.clone(), org, admin_token);
        info!("Influx client is: {:?}", influxdb_client);

        // create the TestInflux as soon as the url is known
        // when this is dropped, the docker container will be stopped
        let test_influx = Self {
            host: influx_host,
            org: org.to_string(),
            token: admin_token.to_string(),
            bucket: init_bucket.to_string(),
            container_name: container_name.clone(),
            client: influxdb_client,
        };

        let start = Instant::now();
        let max_wait = Duration::from_secs(30);
        loop {
            if start.elapsed() > max_wait {
                panic!("influx took too long to start");
            }

            if TcpStream::connect(format!("{}:{}", influx_ip, influx_port))
                .await
                .is_ok()
            {
                break;
            };

            // not open yet. sleep and then try again
            sleep(Duration::from_secs(1)).await;
        }

        sleep(Duration::from_secs(1)).await;

        // TODO: try to use the influx client

        info!(?test_influx, elapsed=%start.elapsed().as_secs_f32(), "influx port is open");

        test_influx
    }
}

impl Drop for TestInflux {
    fn drop(&mut self) {
        info!(%self.container_name, "killing influx");

        let _ = SyncCommand::new("docker")
            .args(["kill", "-s", "9", &self.container_name])
            .output();
    }
}
