use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::oneshot;

use serde::{Serialize, Deserialize};

use tauri::{AppHandle, Manager};

use crate::errors::*;
use crate::clientinfo::{self, Client};
use crate::credentials::Credentials;
use crate::ipc::{Approval, AwsRequestNotification};
use crate::state::AppState;
use crate::shortcuts::{self, ShortcutAction};

#[cfg(windows)]
mod server_win;
#[cfg(windows)]
pub use server_win::Server;
#[cfg(windows)]
use server_win::Stream;

#[cfg(unix)]
mod server_unix;
#[cfg(unix)]
pub use server_unix::Server;
#[cfg(unix)]
use server_unix::Stream;


#[derive(Serialize, Deserialize)]
pub enum Request {
    GetAwsCredentials{ 
        base: bool,
    },
    InvokeShortcut(ShortcutAction),
}


#[derive(Debug, Serialize, Deserialize)]
pub enum Response {
    Aws(Credentials),
    Empty,
}


async fn handle(mut stream: Stream, app_handle: AppHandle, client_pid: u32) -> Result<(), HandlerError> 
{
    // read from stream until delimiter is reached
    let mut buf: Vec<u8> = Vec::with_capacity(1024); // requests are small, 1KiB is more than enough
    let mut n = 0;
    loop {
        n += stream.read_buf(&mut buf).await?;
        if let Some(&b'\n') = buf.last() {
            break;
        }
        else if n >= 1024 {
            return Err(HandlerError::RequestTooLarge);
        }
    }

    let client = clientinfo::get_process_parent_info(client_pid)?;

    let req: Request = serde_json::from_slice(&buf)?;
    let res = match req {
        Request::GetAwsCredentials{ base } => get_aws_credentials(base, client, app_handle).await,
        Request::InvokeShortcut(action) => invoke_shortcut(action).await,
    };

    let res = serde_json::to_vec(&res).unwrap();
    stream.write_all(&res).await?;
    Ok(())
}


async fn invoke_shortcut(action: ShortcutAction) -> Result<Response, HandlerError> {
    shortcuts::exec_shortcut(action);
    Ok(Response::Empty)
}


async fn get_aws_credentials(base: bool, client: Client, app_handle: AppHandle) -> Result<Response, HandlerError> {
    let state = app_handle.state::<AppState>();
    let rehide_ms = {
        let config = state.config.read().await;
        config.rehide_ms
    };
    let lease = state.acquire_visibility_lease(rehide_ms).await
        .map_err(|_e| HandlerError::NoMainWindow)?; // automate this conversion eventually?

    let (chan_send, chan_recv) = oneshot::channel();
    let request_id = state.register_request(chan_send).await;

    // if an error occurs in any of the following, we want to abort the operation
    // but ? returns immediately, and we want to unregister the request before returning
    // so we bundle it all up in an async block and return a Result so we can handle errors
    let proceed = async {
        let notification = AwsRequestNotification {id: request_id, client, base};
        app_handle.emit_all("credentials-request", &notification)?;

        match chan_recv.await {
            Ok(Approval::Approved) => {
                if base {
                    let creds = state.base_creds_cloned().await?;
                    Ok(Response::Aws(Credentials::Base(creds)))
                }
                else {
                    let creds = state.session_creds_cloned().await?;
                    Ok(Response::Aws(Credentials::Session(creds)))
                }
            },
            Ok(Approval::Denied) => Err(HandlerError::Denied),
            Err(_e) => Err(HandlerError::Internal),
        }
    };

    let result = match proceed.await {
        Ok(r) => Ok(r),
        Err(e) => {
            state.unregister_request(request_id).await;
            Err(e)
        }
    };

    lease.release();
    result
}
