use sqlx::types::uuid::Uuid;
use tauri::{AppHandle, Manager};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::oneshot;

use crate::clientinfo::{self, Client};
use crate::credentials::{
    Credential,
    CredentialRecord,
    Crypto
};
use crate::errors::*;
use crate::ipc::{Approval, AwsRequestNotification, RequestNotificationDetail, RequestResponse};
use crate::shortcuts::{self, ShortcutAction};
use crate::state::AppState;
use super::{
    CloseWaiter,
    CliCredential,
    CliRequest,
    CliResponse,
    Stream,
};


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


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;
        }
        // sanity check, no request should ever be within a mile of 1MB
        else if n >= (1024 * 1024) {
            return Err(HandlerError::RequestTooLarge);
        }
    }

    let client = clientinfo::get_client(client_pid, true)?;
    let waiter = CloseWaiter { stream: &mut stream };


    let req: CliRequest = serde_json::from_slice(&buf)?;
    let res = match req {
        CliRequest::GetAwsCredential{ name, base } => get_aws_credentials(
            name, base, client, app_handle, waiter
        ).await,
        CliRequest::GetDockerCredential{ server_url } => get_docker_credentials (
            server_url, client, app_handle, waiter
        ).await,
        CliRequest::SaveCredential{ name, is_default, credential } => save_credential(
            name, is_default, credential, app_handle
        ).await,
        CliRequest::InvokeShortcut(action) => invoke_shortcut(action).await,
    };

    // doesn't make sense to send the error to the client if the client has already left
    if let Err(HandlerError::Abandoned) = res {
        return Err(HandlerError::Abandoned);
    }

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


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


async fn get_aws_credentials(
    name: Option<String>,
    base: bool,
    client: Client,
    app_handle: AppHandle,
    waiter: CloseWaiter<'_>,
) -> Result<CliResponse, HandlerError> {
    let detail = RequestNotificationDetail::new_aws(client, name.clone(), base);
    let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
    match response.approval {
        Approval::Approved => {
            let state = app_handle.state::<AppState>();
            if response.base {
                let creds = state.get_aws_base(name).await?;
                Ok(CliResponse::Credential(CliCredential::AwsBase(creds)))
            }
            else {
                let creds = state.get_aws_session(name).await?.clone();
                Ok(CliResponse::Credential(CliCredential::AwsSession(creds)))
            }
        },
        Approval::Denied => Err(HandlerError::Denied),
    }
}

async fn get_docker_credentials(
    server_url: String,
    client: Client,
    app_handle: AppHandle,
    waiter: CloseWaiter<'_>,
) -> Result<CliResponse, HandlerError> {
    let detail = RequestNotificationDetail::new_docker(client, server_url.clone());
    let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
    match response.approval {
        Approval::Approved => {
            let state = app_handle.state::<AppState>();
            let creds = state.get_docker_credential(&server_url).await?;
            Ok(CliResponse::Credential(CliCredential::Docker(creds)))
        },
        Approval::Denied => {
            Err(HandlerError::Denied)
        },
    }
}

pub async fn save_credential(
    name: String,
    is_default: bool,
    credential: Credential,
    app_handle: AppHandle,
) -> Result<CliResponse, HandlerError> {
    let state = app_handle.state::<AppState>();

    // eventually ask the frontend to unlock here

    // a bit weird but convenient
    let random_bytes = Crypto::salt();
    let id = Uuid::from_slice(&random_bytes[..16]).unwrap();

    let record = CredentialRecord {
        id, name, is_default, credential
    };
    state.save_credential(record).await?;

    Ok(CliResponse::Empty)
}
