//! Backend service running on a dedicated thread with a tokio LocalSet.
//!
//! All server-facing work (capnp-rpc, node_service::Client) is !Send and must run on this
//! single thread. The UI thread sends commands over a channel; this thread runs
//! `LocalSet::run_until` for each request and sends the result back.

use std::path::PathBuf;
use std::sync::mpsc;
use std::thread;

use tokio::runtime::Builder;
use tokio::task::LocalSet;

use quicproquo_client::{cmd_health_json, whoami_json};

/// Commands the UI can send to the backend thread.
pub enum BackendCommand {
    Whoami {
        state_path: String,
        password: Option<String>,
    },
    Health {
        server: String,
        ca_cert: PathBuf,
        server_name: String,
    },
}

/// Response sent back to the UI.
pub type BackendResponse = Result<String, String>;

/// Spawn the backend thread and return a sender to post commands and a join handle.
/// The backend runs a tokio LocalSet and processes one command at a time:
/// for each received command it runs `LocalSet::run_until(future)` (for async commands)
/// or runs sync code (whoami), then sends the result on the provided reply channel.
pub fn spawn_backend() -> (mpsc::Sender<(BackendCommand, mpsc::Sender<BackendResponse>)>, thread::JoinHandle<()>) {
    let (tx, rx) = mpsc::channel::<(BackendCommand, mpsc::Sender<BackendResponse>)>();

    let handle = thread::spawn(move || {
        let rt = Builder::new_current_thread()
            .enable_all()
            .build()
            .expect("backend tokio runtime");
        let local = LocalSet::new();

        while let Ok((cmd, reply_tx)) = rx.recv() {
            let result = run_command(&local, &rt, cmd);
            let _ = reply_tx.send(result);
        }
    });

    (tx, handle)
}

fn run_command(
    local: &LocalSet,
    rt: &tokio::runtime::Runtime,
    cmd: BackendCommand,
) -> BackendResponse {
    match cmd {
        BackendCommand::Whoami { state_path, password } => {
            let path = PathBuf::from(&state_path);
            whoami_json(&path, password.as_deref()).map_err(|e| e.to_string())
        }
        BackendCommand::Health {
            server,
            ca_cert,
            server_name,
        } => {
            // Request-response: we run LocalSet::run_until for this single request so capnp-rpc
            // and connect_node stay on this thread (!Send).
            let fut = cmd_health_json(&server, &ca_cert, &server_name);
            rt.block_on(local.run_until(fut)).map_err(|e| e.to_string())
        }
    }
}

/// Default CA cert path (relative to cwd or absolute); same default as CLI.
pub fn default_ca_cert() -> PathBuf {
    PathBuf::from("data/server-cert.der")
}

/// Default TLS server name.
pub fn default_server_name() -> String {
    "localhost".to_string()
}
