#[cfg(windows)]
use tokio::net::windows::named_pipe::{
    NamedPipeServer,
    ServerOptions,
};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::oneshot;

use serde::{Serialize, Deserialize};

use tauri::{
    AppHandle,
    Manager,
    async_runtime as rt,
};

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};


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


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


pub struct Server {
    listener: tokio::net::windows::named_pipe::NamedPipeServer,
    app_handle: AppHandle,
}

impl Server {
    pub fn start(app_handle: AppHandle) -> std::io::Result<()> {
        let listener = ServerOptions::new()
            .first_pipe_instance(true)
            .create(r"\\.\pipe\creddy-requests")?;

        let srv = Server {listener, app_handle};
        rt::spawn(srv.serve());
        Ok(())
    }

    async fn serve(mut self) {
        loop {
            if let Err(e) = self.try_serve().await {
                eprintln!("Error accepting connection: {e}");
            }
        }
    }

    async fn try_serve(&mut self) -> std::io::Result<()> {
        // connect() just waits for a client to connect, it doesn't return anything
        self.listener.connect().await?;

        // create a new pipe instance to listen for the next client, and swap it in
        let new_listener = ServerOptions::new().create(r"\\.\pipe\creddy-requests")?;
        let mut stream = std::mem::replace(&mut self.listener, new_listener);
        let new_handle = self.app_handle.app_handle();
        rt::spawn(async move {
            let res = serde_json::to_string(
                &handle(&mut stream, new_handle).await
            ).unwrap();
            if let Err(e) = stream.write_all(res.as_bytes()).await {
                eprintln!("Error responding to request: {e}");
            }
        });

        Ok(())
    }
}


async fn handle(stream: &mut NamedPipeServer, app_handle: AppHandle) -> Result<Response, 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_client_parent(&stream)?;

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


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
}
