use std::sync::{
    Arc,
    Mutex,
};

use anyhow::Error;
use futures::{
    prelude::*,
    select,
};
use ngrok::prelude::*;
use tokio::sync::oneshot;
use tracing::info;
use url::Url;

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .pretty()
        .with_env_filter(std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()))
        .init();

    let forwards_to = std::env::args()
        .nth(1)
        .ok_or_else(|| anyhow::anyhow!("missing forwarding address"))
        .and_then(|s| Ok(Url::parse(&s)?))?;

    loop {
        let (stop_tx, stop_rx) = oneshot::channel();
        let stop_tx = Arc::new(Mutex::new(Some(stop_tx)));

        let (restart_tx, restart_rx) = oneshot::channel();
        let restart_tx = Arc::new(Mutex::new(Some(restart_tx)));

        let mut fwd = ngrok::Session::builder()
            .authtoken_from_env()
            .handle_stop_command(move |req| {
                let stop_tx = stop_tx.clone();
                async move {
                    info!(?req, "received stop command");
                    let _ = stop_tx.lock().unwrap().take().unwrap().send(());
                    Ok(())
                }
            })
            .handle_restart_command(move |req| {
                let restart_tx = restart_tx.clone();
                async move {
                    info!(?req, "received restart command");
                    let _ = restart_tx.lock().unwrap().take().unwrap().send(());
                    Ok(())
                }
            })
            .handle_update_command(|req| async move {
                info!(?req, "received update command");
                Err("unable to update".into())
            })
            .connect()
            .await?
            .http_endpoint()
            .listen_and_forward(forwards_to.clone())
            .await?;

        info!(url = fwd.url(), %forwards_to, "started forwarder");

        let mut fut = fwd.join().fuse();
        let mut stop_rx = stop_rx.fuse();
        let mut restart_rx = restart_rx.fuse();

        select! {
            res = fut => info!("{:?}", res?),
            _ = stop_rx => return Ok(()),
            _ = restart_rx => {
                drop(fut);
                let _ = fwd.close().await;
                continue
            },
        }
    }
}
