// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

use {
    anyhow::{Context, Error},
    diagnostics_reader::{ArchiveReader, ComponentSelector, Inspect},
    fidl_fuchsia_sys::{
        EnvironmentControllerEvent, EnvironmentControllerProxy, EnvironmentMarker,
        EnvironmentOptions, EnvironmentProxy, LauncherProxy,
    },
    fuchsia_async as fasync,
    fuchsia_component::client::{connect_to_protocol, launch},
    fuchsia_inspect::assert_data_tree,
    fuchsia_zircon::Duration,
    futures::stream::StreamExt,
    lazy_static::lazy_static,
};

lazy_static! {
    static ref TEST_COMPONENT: String = "test_component.cmx".to_string();
    static ref TEST_COMPONENT_URL: String =
        "fuchsia-pkg://fuchsia.com/component_events_integration_tests#meta/test_component.cmx"
            .to_string();
    static ref DIAGNOSTICS_ARGUMENTS: Vec<String> = vec!["with-diagnostics".to_string()];
}

// Validates that the realm paths with which the observer queries do not include the current realm.
#[fasync::run_singlethreaded(test)]
async fn test_observer_integration() -> Result<(), Error> {
    let env = connect_to_protocol::<EnvironmentMarker>().expect("connect to current environment");
    let (env1, _env1_ctrl, launcher1) = create_nested_environment(&env, "env1").await?;
    let (_env2, _env2_ctrl, launcher2) = create_nested_environment(&env1, "env2").await?;

    let _app_1 =
        launch(&launcher1, TEST_COMPONENT_URL.to_string(), Some(DIAGNOSTICS_ARGUMENTS.clone()))?;
    let _app_2 =
        launch(&launcher2, TEST_COMPONENT_URL.to_string(), Some(DIAGNOSTICS_ARGUMENTS.clone()))?;

    // The observer must have received results with a correct relative realm path (not including
    // the test realm).
    check_nested(Some(vec!["env1".to_string()]), 1).await?;
    check_nested(Some(vec!["env1".to_string(), "env2".to_string()]), 1).await?;

    // The observer must not have received a result containing the test realm.
    check_nested(Some(vec!["*".to_string(), "env1".to_string()]), 0).await?;

    // If no component selector is set, all are returned.
    check_nested(None, 3).await?;

    Ok(())
}

async fn check_nested(
    realm_path: Option<Vec<String>>,
    expected_results: usize,
) -> Result<(), Error> {
    let mut fetcher = ArchiveReader::new();
    if expected_results == 0 {
        fetcher = fetcher.with_timeout(Duration::from_seconds(5));
    }
    if let Some(mut moniker) = realm_path {
        moniker.push(TEST_COMPONENT.clone());
        fetcher = fetcher.add_selector(ComponentSelector::new(moniker));
    }
    let results = fetcher.snapshot::<Inspect>().await?.into_iter().collect::<Vec<_>>();
    assert_eq!(results.len(), expected_results);
    for result in results {
        assert_data_tree!(result.payload.as_ref().unwrap(), root: contains {
            "fuchsia.inspect.Health": contains {
                status: "OK",
            }
        });
    }
    Ok(())
}

async fn create_nested_environment(
    parent: &EnvironmentProxy,
    label: &str,
) -> Result<(EnvironmentProxy, EnvironmentControllerProxy, LauncherProxy), Error> {
    let (new_env, new_env_server_end) =
        fidl::endpoints::create_proxy::<EnvironmentMarker>().context("could not create proxy")?;
    let (controller, controller_server_end) =
        fidl::endpoints::create_proxy().context("could not create proxy")?;

    // Component manager will run with these environment options
    let mut env_options = EnvironmentOptions {
        inherit_parent_services: true,
        use_parent_runners: true,
        kill_on_oom: true,
        delete_storage_on_death: true,
    };

    parent
        .create_nested_environment(
            new_env_server_end,
            controller_server_end,
            label,
            None,
            &mut env_options,
        )
        .context("could not create isolated environment")?;

    let EnvironmentControllerEvent::OnCreated {} = controller
        .take_event_stream()
        .next()
        .await
        .unwrap()
        .expect("failed to get env created event");

    let (launcher, launcher_server_end) =
        fidl::endpoints::create_proxy().context("could not create proxy")?;
    new_env
        .get_launcher(launcher_server_end)
        .context("could not get isolated environment launcher")?;

    Ok((new_env, controller, launcher))
}
