//! Entry point for `contacts-server`.
//!
//! Boot sequence:
//!
//! 1. Open SQLite, run migrations, log readiness.
//! 2. Open a session-bus connection dedicated to EDS push and build the
//!    `EdsPusher`.
//! 3. Register `org.liberux.Contacts1` on the session bus, handing the
//!    pusher to the service so every mutation can poke EDS.
//! 4. Bind the CardDAV loopback listener (gets the ephemeral port).
//! 5. Best-effort: write EDS `.source` files for the address book and
//!    its parent collection.
//! 6. Best-effort: stash the CardDAV Basic-auth token in the user's
//!    Secret Service collection under the EDS-conventional attributes.
//! 7. Enter the CardDAV accept loop forever (returns only on a fatal
//!    accept error or signal).

mod carddav;
mod config;
mod db;
mod dbus;
mod eds_push;
mod eds_register;
mod model;
mod secret;

use std::collections::HashMap;
use std::sync::Arc;

use anyhow::{ Context, Result };

fn main() -> Result<()>
{
	env_logger::Builder::from_env(
		env_logger::Env::default().default_filter_or( "info" ),
	).init();

	let paths = config::Paths::resolve().context( "resolving XDG paths" )?;
	paths.ensure_dirs().context( "ensuring data/config/runtime dirs" )?;

	log::info!( "data dir:    {}", paths.data_dir.display() );
	log::info!( "config dir:  {}", paths.config_dir.display() );
	if let Some( rt ) = &paths.runtime_dir
	{
		log::info!( "runtime dir: {}", rt.display() );
	}

	let database = Arc::new(
		db::Database::open( &paths.contacts_db )
			.with_context( || format!( "opening {}", paths.contacts_db.display() ) )?,
	);

	database.with_conn( |conn| -> Result<()>
	{
		let counter = db::sync_state::change_counter( conn )?;
		let uuid    = db::sync_state::instance_uuid( conn )?;
		log::info!(
			"schema ready: change_counter = {}, instance_uuid = {}",
			counter, uuid,
		);
		Ok( () )
	} )?;

	let token = carddav::auth::Token::load_or_generate( &paths.token_file )
		.with_context( || format!( "loading token at {}", paths.token_file.display() ) )?;

	smol::block_on( async
	{
		// EDS push uses its own session-bus connection — building the
		// pusher before `dbus::serve` lets the service own a clone from
		// the start so we don't have to inject it later via the object
		// server. The pusher itself doesn't contact EDS until something
		// actually calls `refresh()`.
		let eds_bus = zbus::Connection::session().await
			.context( "opening session bus for EDS push" )?;
		let pusher = eds_push::EdsPusher::for_address_book( eds_bus )
			.context( "constructing EDS pusher" )?
			.into_arc();

		let dbus_conn = dbus::serve( database.clone(), Some( pusher.clone() ) )
			.await
			.context( "registering D-Bus service" )?;
		log::info!(
			"D-Bus service registered: {} at {}",
			dbus::SERVICE_NAME, dbus::OBJECT_PATH,
		);

		let bound = carddav::bind( carddav::BindConfig
		{
			token:     token.clone(),
			port_file: paths.port_file.as_deref(),
		} ).await.context( "binding CardDAV listener" )?;
		let port = bound.addr.port();

		// Phase 5 side-channel registrations. Both are best-effort: a
		// failure here doesn't stop the daemon from coming up — CardDAV
		// is still reachable for clients that add the URL manually.
		register_with_eds( &paths, port );
		register_with_secret_service( &dbus_conn, &paths, &token ).await;

		// `dbus_conn` stays in scope until this async block ends — that
		// keeps `org.liberux.Contacts1` registered for the lifetime of
		// the accept loop below; dropping the connection would tear the
		// service name down.
		bound.run( database.clone() ).await
			.context( "CardDAV server" )
	} )?;

	Ok( () )
}

fn register_with_eds( paths: &config::Paths, port: u16 )
{
	match eds_register::register( paths, port )
	{
		Ok( eds_register::RegistrationOutcome::NotInstalled ) => {}
		Ok( eds_register::RegistrationOutcome::OptedOut )     => {}
		Ok( eds_register::RegistrationOutcome::Registered { wrote, untouched } ) =>
		{
			for p in &wrote
			{
				log::info!( "EDS source written: {}", p.display() );
			}
			for p in &untouched
			{
				log::debug!( "EDS source unchanged: {}", p.display() );
			}
		}
		Err( e ) => log::warn!( "EDS auto-registration failed: {:#}", e ),
	}
}

async fn register_with_secret_service(
	bus: &zbus::Connection,
	paths: &config::Paths,
	token: &carddav::auth::Token,
)
{
	let mut attrs: HashMap<String, String> = HashMap::new();
	attrs.insert(
		secret::eds_attributes::SOURCE_UID_KEY.to_owned(),
		secret::eds_attributes::COLLECTION_SOURCE_UID.to_owned(),
	);

	let outcome = secret::store_token(
		bus,
		secret::eds_attributes::SCHEMA_NAME,
		attrs,
		"Liberux Contacts password",
		token.as_str().as_bytes(),
	).await;

	if let Err( e ) = outcome
	{
		log::warn!( "Secret Service token storage failed: {:#}", e );
		log::warn!(
			"Falling back to file-only token at {}; EDS may prompt for it on first connect",
			paths.token_file.display(),
		);
	}
}
