//! Mount gRPC server reflection (`grpc.reflection.v1` + `v1alpha`) on a
//! Connect router, advertising exactly the services that router registers.

use connectrpc::Router;
use connectrpc_reflection::{
    ReflectionError, Reflector, SERVER_REFLECTION_SERVICE_NAME,
    SERVER_REFLECTION_V1ALPHA_SERVICE_NAME,
};

/// Install server reflection on `router`, advertising the services derived
/// from its registered method paths plus the two reflection services.
///
/// Deriving from the router's `pkg.Service/Method` paths means a newly
/// mounted service can never be forgotten from the advertised list.
/// `with_services` replaces the advertised list verbatim and grpc-go always
/// lists the reflection services, so they ride along explicitly.
///
/// Call this after every service is registered: the advertised list is a
/// snapshot of the router at this point.
///
/// # Errors
///
/// Returns [`ReflectionError`] if the generated descriptor pool cannot back
/// a reflector — a codegen defect, not runtime input.
pub fn install_reflection(router: Router) -> Result<Router, ReflectionError> {
    let mut services: Vec<String> = router
        .methods()
        .filter_map(|path| path.split_once('/').map(|(svc, _)| svc.to_owned()))
        .collect();
    services.sort_unstable();
    services.dedup();
    services.push(SERVER_REFLECTION_SERVICE_NAME.to_owned());
    services.push(SERVER_REFLECTION_V1ALPHA_SERVICE_NAME.to_owned());
    let reflector =
        Reflector::from_descriptor_pool(crate::descriptor_pool().clone())?.with_services(services);
    Ok(connectrpc_reflection::install(router, reflector))
}

#[cfg(test)]
mod tests {
    use super::install_reflection;

    /// The advertised list is derived from the router, so an empty router
    /// advertises only the reflection services themselves.
    #[test]
    fn empty_router_advertises_only_reflection() {
        let router = connectrpc::Router::new();
        let installed = install_reflection(router).expect("pool backs a reflector");
        assert!(
            installed
                .methods()
                .any(|m| m.starts_with("grpc.reflection.v1.")),
            "v1 reflection registered"
        );
        assert!(
            installed
                .methods()
                .any(|m| m.starts_with("grpc.reflection.v1alpha.")),
            "v1alpha reflection registered"
        );
    }

    #[test]
    fn pool_contains_known_symbols() {
        let pool = crate::descriptor_pool();
        for fqn in [
            "makechain.MakechainService",
            "makechain.Message",
            "makechain.MessageData",
            "makechain.Block",
            "makechain.BlockHeader",
            "makechain.SubmitMessageRequest",
        ] {
            assert!(
                pool.file_containing_symbol(fqn).is_some(),
                "expected {fqn} to resolve via DescriptorPool"
            );
        }
        assert!(
            pool.file_containing_symbol("makechain.does.not.Exist")
                .is_none()
        );
    }
}
