use std::process::Command;
use std::time::Duration;

use tauri::{AppHandle, Manager};
use tokio::time::sleep;

use crate::app::APP;
use crate::errors::*;
use crate::state::AppState;


pub async fn launch(use_base: bool) -> Result<(), LaunchTerminalError> {
    let app = APP.get().unwrap();
    let state = app.state::<AppState>();

    // register_terminal_request() returns Err if there is another request pending
    if state.register_terminal_request().await.is_err() {
        return Ok(());
    }

    let res = do_launch(app, use_base).await;

    state.unregister_terminal_request().await;
    res
}


// this handles most of the work, the outer function is just to ensure we properly
// unregister the request if there's an error
async fn do_launch(app: &AppHandle, use_base: bool) -> Result<(), LaunchTerminalError> {
    let state = app.state::<AppState>();

    let mut cmd = {
        let config = state.config.read().await;
        let mut cmd = Command::new(&config.terminal.exec);
        cmd.args(&config.terminal.args);
        cmd
    };

    // if session is locked, wait for credentials from frontend
    if state.is_locked().await {
        let lease = state.acquire_visibility_lease(0).await
            .map_err(|_e| LaunchTerminalError::NoMainWindow)?; // automate conversion eventually?

        let (tx, rx) = tokio::sync::oneshot::channel();
        app.once("unlocked", move |_| {
            let _ = tx.send(());
        });

        let timeout = Duration::from_secs(60);
        tokio::select! {
            // if the frontend is unlocked within 60 seconds, release visibility lock and proceed
            _ = rx => lease.release(),
            // otherwise, dump this request, but return Ok so we don't get an error popup
            _ = sleep(timeout) => {
                eprintln!("WARNING: Request to launch terminal timed out after 60 seconds.");
                return Ok(());
            },
        }
    }

    // session should really be unlocked at this point, but if the frontend misbehaves
    // (i.e. lies about unlocking) we could end up here with a locked session
    // this will result in an error popup to the user (see main hotkey handler)
    if use_base {
        let base_creds = state.get_aws_base(None).await?;
        cmd.env("AWS_ACCESS_KEY_ID", &base_creds.access_key_id);
        cmd.env("AWS_SECRET_ACCESS_KEY", &base_creds.secret_access_key);
    }
    else {
        let session_creds = state.get_aws_session(None).await?;
        cmd.env("AWS_ACCESS_KEY_ID", &session_creds.access_key_id);
        cmd.env("AWS_SECRET_ACCESS_KEY", &session_creds.secret_access_key);
        cmd.env("AWS_SESSION_TOKEN", &session_creds.session_token);
    }

    match cmd.spawn() {
        Ok(_) => Ok(()),
        Err(e) if std::io::ErrorKind::NotFound == e.kind() => {
            Err(ExecError::NotFound(cmd.get_program().to_owned()))
        },
        Err(e) => Err(ExecError::ExecutionFailed(e)),
    }?;

    Ok(())
}
