// Privileged backend, run as root by eydos-installer-daemon.service.
//
// Listens on the [`crate::ipc::SOCKET_PATH`] Unix socket and executes
// the requests the unprivileged wizard cannot: the real install engine
// and the lifecycle-marker swap that ends the Configure flow. One
// install at a time; progress is streamed back as JSON lines.

use std::io::{ BufRead, BufReader };
use std::os::unix::net::{ UnixListener, UnixStream };
use std::path::Path;
use std::sync::atomic::{ AtomicBool, Ordering };
use std::time::Duration;

use crate::install;
use crate::ipc::{ self, Event, Request, SOCKET_PATH };

static BUSY: AtomicBool = AtomicBool::new( false );

pub fn run() -> Result<(), String>
{
	let path = Path::new( SOCKET_PATH );
	if let Some( dir ) = path.parent()
	{
		std::fs::create_dir_all( dir ).map_err( |e| format!( "mkdir {}: {e}", dir.display() ) )?;
	}
	let _ = std::fs::remove_file( path );
	let listener = UnixListener::bind( path ).map_err( |e| format!( "bind {SOCKET_PATH}: {e}" ) )?;
	// World-connectable: the wizard runs as whatever user the session
	// has. Destructive requests are gated on the install-pending
	// marker, so this surface only exists on the install medium.
	use std::os::unix::fs::PermissionsExt;
	std::fs::set_permissions( path, std::fs::Permissions::from_mode( 0o666 ) )
		.map_err( |e| format!( "chmod {SOCKET_PATH}: {e}" ) )?;

	eprintln!( "eydos-installer daemon: listening on {SOCKET_PATH}" );
	for stream in listener.incoming()
	{
		match stream
		{
			Ok( s )  => { std::thread::spawn( move || handle( s ) ); }
			Err( e ) => eprintln!( "daemon: accept: {e}" ),
		}
	}
	Ok( () )
}

fn handle( stream: UnixStream )
{
	let reader = match stream.try_clone()
	{
		Ok( s )  => s,
		Err( e ) => { eprintln!( "daemon: clone socket: {e}" ); return; }
	};
	let mut writer = stream;
	let mut line = String::new();
	if BufReader::new( reader ).read_line( &mut line ).is_err() { return; }

	let request: Request = match serde_json::from_str( line.trim() )
	{
		Ok( r )  => r,
		Err( e ) =>
		{
			send( &mut writer, &Event::Failed { message: format!( "bad request: {e}" ) } );
			return;
		}
	};

	match request
	{
		Request::Install { plan } => run_install( &mut writer, plan ),
		Request::CancelInstall =>
		{
			eprintln!( "daemon: install cancellation requested" );
			install::request_cancel();
			send( &mut writer, &Event::Done );
		}
		Request::DiskOps { ops } =>
		{
			if !Path::new( install::INSTALL_PENDING ).exists()
			{
				send( &mut writer, &Event::Failed { message: "not-pending".to_string() } );
				return;
			}
			eprintln!( "daemon: applying {} disk operation(s)", ops.len() );
			let errors = install::apply_disk_ops( &ops );
			if errors.is_empty()
			{
				send( &mut writer, &Event::Done );
			}
			else
			{
				send( &mut writer, &Event::Failed { message: errors.join( "\n" ) } );
			}
		}
		Request::ApplyLocale { locale } =>
		{
			if !Path::new( install::CONFIGURE_PENDING ).exists()
			{
				send( &mut writer, &Event::Failed { message: "not-pending".to_string() } );
				return;
			}
			eprintln!( "daemon: applying locale {locale} to the running system" );
			install::apply_locale( Path::new( "/" ), &locale );
			send( &mut writer, &Event::Done );
		}
		Request::StageAccount { username, hostname, password, encrypt } =>
		{
			if !Path::new( install::CONFIGURE_PENDING ).exists()
			{
				send( &mut writer, &Event::Failed { message: "not-pending".to_string() } );
				return;
			}
			eprintln!( "daemon: staging account {username} (encrypt: {encrypt}) for the next boot" );
			match crate::firstboot::stage( &username, &hostname, &password, encrypt )
			{
				Ok( () ) => send( &mut writer, &Event::Done ),
				Err( e ) => send( &mut writer, &Event::Failed { message: e } ),
			};
		}
		Request::FinishConfigure  => finish_configure( &mut writer ),
	}
}

fn run_install( writer: &mut UnixStream, mut plan: install::InstallPlan )
{
	// "not-pending" / "busy" are tokens the wizard localizes.
	if !Path::new( install::INSTALL_PENDING ).exists()
	{
		send( writer, &Event::Failed { message: "not-pending".to_string() } );
		return;
	}
	if BUSY.swap( true, Ordering::SeqCst )
	{
		send( writer, &Event::Failed { message: "busy".to_string() } );
		return;
	}

	plan.test = false;
	eprintln!( "daemon: install requested: {:?} on {}", plan.mode, plan.disk.display() );
	let slot = install::start( plan );

	// A vanished client must not abort a half-written disk: keep
	// draining the engine to completion, just stop reporting.
	let mut client_alive = true;
	let mut last = ( install::Phase::Partition, -1.0f32 );
	loop
	{
		std::thread::sleep( Duration::from_millis( 200 ) );
		let status = match slot.lock()
		{
			Ok( s )  => s.clone(),
			Err( _ ) => break,
		};
		if client_alive && ( status.phase, status.fraction ) != last
		{
			last = ( status.phase, status.fraction );
			client_alive = send( writer, &Event::Progress { phase: status.phase, fraction: status.fraction } );
		}
		if let Some( message ) = status.error
		{
			eprintln!( "daemon: install failed: {message}" );
			if client_alive { send( writer, &Event::Failed { message } ); }
			break;
		}
		if status.done
		{
			eprintln!( "daemon: install finished" );
			if client_alive { send( writer, &Event::Done ); }
			break;
		}
	}
	BUSY.store( false, Ordering::SeqCst );
}

/// Idempotent end-of-configure marker swap: retire `configure-pending`,
/// record `configured`.
fn finish_configure( writer: &mut UnixStream )
{
	let _ = std::fs::remove_file( install::CONFIGURE_PENDING );
	match std::fs::write( install::CONFIGURED, "" )
	{
		Ok( () ) => { send( writer, &Event::Done ); }
		Err( e ) => { send( writer, &Event::Failed { message: format!( "write {}: {e}", install::CONFIGURED ) } ); }
	}
}

fn send( writer: &mut UnixStream, event: &Event ) -> bool
{
	ipc::send( writer, event ).map_err( |e| eprintln!( "daemon: {e}" ) ).is_ok()
}
