use std::process::Stdio;

use bifrostlink::declarative::endpoints;
use bifrostlink::Config;
use serde::{Deserialize, Serialize};
use std::result::Result;
use tokio::process::Command;

pub const NIX_DAEMON_SOCKET: &str = "/nix/var/nix/daemon-socket/socket";

pub struct NixDaemon;

#[derive(Serialize, Deserialize, Debug, thiserror::Error)]
pub enum Error {
	#[error("nix daemon unavailable: {0}")]
	DaemonUnavailable(String),
	#[error("tunnel socket unavailable: {0}")]
	Tunnel(String),
}

#[endpoints(ns = 4)]
impl NixDaemon {
	#[endpoints(id = 1)]
	async fn connect_daemon(&self, socket: String) -> Result<(), Error> {
		let mut daemon = tokio::net::UnixStream::connect(NIX_DAEMON_SOCKET)
			.await
			.map_err(|e| Error::DaemonUnavailable(e.to_string()))?;
		let mut tunnel = tokio::net::UnixStream::connect(&socket)
			.await
			.map_err(|e| Error::Tunnel(e.to_string()))?;
		tokio::spawn(async move {
			if let Err(e) = tokio::io::copy_bidirectional(&mut daemon, &mut tunnel).await {
				tracing::debug!("nix daemon tunnel ended: {e}");
			}
		});
		Ok(())
	}

	#[endpoints(id = 2)]
	async fn serve_store(&self, store: String, socket: String) -> Result<(), Error> {
		let mut child = Command::new("nix-daemon")
			.arg("--stdio")
			.arg("--store")
			.arg(&store)
			.stdin(Stdio::piped())
			.stdout(Stdio::piped())
			.spawn()
			.map_err(|e| Error::DaemonUnavailable(e.to_string()))?;
		let tunnel = tokio::net::UnixStream::connect(&socket)
			.await
			.map_err(|e| Error::Tunnel(e.to_string()))?;
		let mut stdin = child.stdin.take().expect("piped");
		let mut stdout = child.stdout.take().expect("piped");
		tokio::spawn(async move {
			let mut tunnel = tunnel;
			let (mut tr, mut tw) = tunnel.split();
			let _ = tokio::join!(
				tokio::io::copy(&mut tr, &mut stdin),
				tokio::io::copy(&mut stdout, &mut tw),
			);
			let _ = child.wait().await;
		});
		Ok(())
	}
}
