use futures::SinkExt;
use ssh_agent_lib::agent::MessageCodec;
use ssh_agent_lib::proto::message::{
    Message,
    SignRequest,
};
use tauri::{AppHandle, Manager};
use tokio_stream::StreamExt;
use tokio::sync::oneshot;
use tokio_util::codec::Framed;

use crate::clientinfo;
use crate::errors::*;
use crate::ipc::{Approval, RequestNotification, RequestNotificationDetail};
use crate::state::AppState;

use super::{CloseWaiter, Stream};


pub fn serve(app_handle: AppHandle) -> std::io::Result<()> {
    super::serve("creddy-agent", app_handle, handle)
}


async fn handle(
    stream: Stream,
    app_handle: AppHandle,
    client_pid: u32
) -> Result<(), HandlerError> {
    let mut adapter = Framed::new(stream, MessageCodec);
    while let Some(message) = adapter.try_next().await? {
        match message {
            Message::RequestIdentities => {
                let resp = list_identities(app_handle.clone()).await?;
                adapter.send(resp).await?;
            },
            Message::SignRequest(req) => {
                // Note: If the client writes more data to the stream *while* at the
                // same time waiting for a resopnse to a previous request, this will
                // corrupt the framing. Clients don't seem to behave that way though?
                let waiter = CloseWaiter { stream: adapter.get_mut() };
                let resp = sign_request(req, app_handle.clone(), client_pid, waiter).await?;

                // have to do this before we send since we can't inspect the message after
                let is_failure = matches!(resp, Message::Failure);
                adapter.send(resp).await?;

                if is_failure {
                    // this way we don't get spammed with requests for other keys
                    // after denying the first
                    break
                }
            },
            _ => adapter.send(Message::Failure).await?,
        };
    }
    Ok(())
}


async fn list_identities(app_handle: AppHandle) -> Result<Message, HandlerError> {
    let state = app_handle.state::<AppState>();
    let identities = state.list_ssh_identities().await?;
    Ok(Message::IdentitiesAnswer(identities))
}


async fn sign_request(
    req: SignRequest,
    app_handle: AppHandle,
    client_pid: u32,
    waiter: CloseWaiter<'_>,
) -> Result<Message, HandlerError> {
    let state = app_handle.state::<AppState>();

    let client = clientinfo::get_client(client_pid, false)?;
    let key_name = state.ssh_name_from_pubkey(&req.pubkey_blob).await?;
    let detail = RequestNotificationDetail::new_ssh(client, key_name.clone());

    let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
    match response.approval {
        Approval::Approved => {
            let key = state.sshkey_by_name(&key_name).await?;
            let sig = key.sign_request(&req)?;
            Ok(Message::SignResponse(sig))
        },
        Approval::Denied => Err(HandlerError::Abandoned),
    }
}
