//! Federation routing: determine whether a recipient is local or remote.

use std::sync::Arc;

use crate::storage::Store;

/// Where a message should be delivered.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Destination {
    /// Recipient is on this server.
    Local,
    /// Recipient's home server is the given domain.
    Remote(String),
}

/// Resolve a recipient identity key to a routing destination.
///
/// 1. Check the `identity_home_servers` table for an explicit mapping.
/// 2. If no mapping exists, assume local (backwards compatible with single-server deployments).
pub fn resolve_destination(
    store: &Arc<dyn Store>,
    recipient_key: &[u8],
    local_domain: &str,
) -> Destination {
    match store.get_identity_home_server(recipient_key) {
        Ok(Some(domain)) if domain != local_domain => Destination::Remote(domain),
        _ => Destination::Local,
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;

    fn test_store() -> (tempfile::TempDir, Arc<dyn Store>) {
        let dir = tempfile::tempdir().unwrap();
        let store: Arc<dyn Store> =
            Arc::new(crate::storage::FileBackedStore::open(dir.path()).unwrap());
        (dir, store)
    }

    #[test]
    fn unknown_identity_routes_local() {
        let (_dir, store) = test_store();
        let dest = resolve_destination(&store, &[1u8; 32], "local.example.com");
        assert_eq!(dest, Destination::Local);
    }

    fn sql_store() -> (tempfile::TempDir, Arc<dyn Store>) {
        let dir = tempfile::tempdir().unwrap();
        let db_path = dir.path().join("test.db");
        let store: Arc<dyn Store> =
            Arc::new(crate::sql_store::SqlStore::open(&db_path, "").unwrap());
        (dir, store)
    }

    #[test]
    fn identity_on_local_domain_routes_local() {
        let (_dir, store) = sql_store();
        // Register the identity as belonging to the local domain.
        store.store_identity_home_server(&[2u8; 32], "local.example.com").unwrap();
        let dest = resolve_destination(&store, &[2u8; 32], "local.example.com");
        assert_eq!(dest, Destination::Local);
    }

    #[test]
    fn identity_on_remote_domain_routes_remote() {
        let (_dir, store) = sql_store();
        store.store_identity_home_server(&[3u8; 32], "remote.example.com").unwrap();
        let dest = resolve_destination(&store, &[3u8; 32], "local.example.com");
        assert_eq!(dest, Destination::Remote("remote.example.com".to_string()));
    }
}
