/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use async_trait::async_trait;
use buck2_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-providers",
    about = "prints out the providers for a target pattern"
)]
pub struct AuditProvidersCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(name = "TARGET_PATTERNS", help = "Patterns to analyze")]
    pub patterns: Vec<String>,

    #[clap(long, conflicts_with_all=&["list", "print-debug"])]
    pub quiet: bool,

    #[clap(
        long,
        short = 'l',
        help = "List the available providers", conflicts_with_all=&["print-debug", "quiet"]
    )]
    pub list: bool,

    #[clap(
        long = "print-debug",
        help = "Print the providers using debug format (very verbose)",
        conflicts_with_all=&["list", "quiet"]
    )]
    pub print_debug: bool,
}

#[async_trait]
impl AuditSubcommand for AuditProvidersCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use async_trait::async_trait;
use buck2_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-visibility",
    about = "Verify the visibility for transitive deps of the specified target(s) on the unconfigured target graph"
)]
pub struct AuditVisibilityCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(name = "TARGET_PATTERNS", help = "Target pattern(s) to analyze.")]
    pub patterns: Vec<String>,
}

#[async_trait]
impl AuditSubcommand for AuditVisibilityCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use async_trait::async_trait;
use buck2_client_ctx::common::CommonCommandOptions;
use buck2_client_ctx::query_args::CommonAttributeArgs;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-output",
    about = "Query the action that produced the output artifact. Does not support BXL, test, scratch, or anon artifacts. If the configuration hash of the output path does not match the current platform configuration, the unconfigured target label will be returned."
)]
pub struct AuditOutputCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(
        name = "OUTPUT_PATH",
        help = "The buck-out path to the build artifact, starting with `buck-out` and including the configuration platform."
    )]
    pub output_path: String,

    #[clap(long)]
    pub json: bool,

    #[clap(flatten)]
    pub query_attributes: CommonAttributeArgs,
}

#[async_trait]
impl AuditSubcommand for AuditOutputCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub mod command;
pub mod parse;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use async_trait::async_trait;
use buck2_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-parse",
    about = "Parses the buck-out path into parts that may be useful (ex: config hash, file path to artifact)."
)]
pub struct AuditParseCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(
        name = "OUTPUT_PATH",
        help = "The buck-out path to the build artifact, starting with `buck-out` and including the configuration platform."
    )]
    pub output_path: String,

    #[clap(long)]
    pub json: bool,

    #[clap(long)]
    pub output_attribute: Vec<String>,
}

#[async_trait]
impl AuditSubcommand for AuditParseCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! Starlark debugging.

pub mod module;
pub mod package_deps;

use async_trait::async_trait;
use buck2_client_ctx::common::CommonCommandOptions;

use crate::starlark::module::StarlarkModuleCommand;
use crate::starlark::package_deps::StarlarkPackageDepsCommand;
use crate::AuditSubcommand;

#[derive(Debug, clap::Subcommand, serde::Serialize, serde::Deserialize)]
#[clap(name = "starlark", about = "Debug Starlark interpreter")]
pub enum StarlarkCommand {
    Module(StarlarkModuleCommand),
    PackageDeps(StarlarkPackageDepsCommand),
}

#[async_trait]
impl AuditSubcommand for StarlarkCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        match self {
            StarlarkCommand::Module(cmd) => &cmd.common_opts,
            StarlarkCommand::PackageDeps(cmd) => &cmd.common_opts,
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_client_ctx::common::CommonCommandOptions;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "module",
    about = "Inspect Starlark module by fully qualified import string like foo//bar:baz.bzl"
)]
pub struct StarlarkModuleCommand {
    #[clap(name = "IMPORT_PATH", help = "Module import path")]
    pub import_path: String,

    #[clap(flatten)]
    pub(crate) common_opts: CommonCommandOptions,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_client_ctx::common::CommonCommandOptions;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "module",
    about = "Inspect Starlark package file all bzl dependencies by package name like foo//bar/baz"
)]
pub struct StarlarkPackageDepsCommand {
    #[clap(name = "PACKAGE", help = "Package")]
    pub package: String,

    #[clap(flatten)]
    pub(crate) common_opts: CommonCommandOptions,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_analysis::analysis::calculation::resolve_queries;
use buck2_audit::analysis_queries::AuditAnalysisQueriesCommand;
use buck2_cli_proto::ClientContext;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::dice::file_ops::HasFileOps;
use buck2_common::pattern::resolve::resolve_target_patterns;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_core::target::label::TargetLabel;
use buck2_node::nodes::configured_frontend::ConfiguredTargetNodeCalculation;
use buck2_node::target_calculation::ConfiguredTargetCalculation;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use buck2_server_ctx::pattern::parse_patterns_from_cli_args;
use buck2_server_ctx::pattern::target_platform_from_client_context;
use dupe::Dupe;
use gazebo::prelude::*;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for AuditAnalysisQueriesCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |server_ctx, mut ctx| {
                let cells = ctx.get_cell_resolver().await?;

                let global_target_platform =
                    target_platform_from_client_context(&client_ctx, server_ctx, &ctx).await?;

                let parsed_patterns = parse_patterns_from_cli_args::<TargetPatternExtra>(
                    &mut ctx,
                    &self
                        .patterns
                        .map(|pat| buck2_data::TargetPattern { value: pat.clone() }),
                    server_ctx.working_dir(),
                )
                .await?;
                let resolved_pattern =
                    resolve_target_patterns(&cells, &parsed_patterns, &ctx.file_ops()).await?;

                let mut stdout = stdout.as_writer();

                for (package, spec) in resolved_pattern.specs {
                    match spec {
                        buck2_core::pattern::PackageSpec::Targets(targets) => {
                            for (target, TargetPatternExtra) in targets {
                                let label = TargetLabel::new(package.dupe(), target.as_ref());
                                let configured_target = ctx
                                    .get_configured_target(&label, global_target_platform.as_ref())
                                    .await?;
                                let node =
                                    ctx.get_configured_target_node(&configured_target).await?;
                                let node = node.require_compatible()?;
                                let query_results = resolve_queries(&ctx, &node).await?;
                                writeln!(stdout, "{}:", label)?;
                                for (query, result) in &query_results {
                                    writeln!(stdout, "  {}", query)?;
                                    for (target, providers) in &**result {
                                        writeln!(stdout, "    {}", target.unconfigured())?;
                                        if self.include_outputs {
                                            let outputs = providers
                                                .provider_collection()
                                                .default_info()
                                                .default_outputs_raw();
                                            writeln!(stdout, "        {}", outputs)?;
                                        }
                                    }
                                }
                            }
                        }
                        buck2_core::pattern::PackageSpec::All => {
                            unimplemented!()
                        }
                    }
                }

                Ok(())
            })
            .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_audit::cell::AuditCellCommand;
use buck2_cli_proto::ClientContext;
use buck2_common::dice::cells::HasCellResolver;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use indexmap::IndexMap;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for AuditCellCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        _client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |server_ctx, ctx| {
                let cells = ctx.get_cell_resolver().await?;
                let fs = server_ctx.project_root();
                let cwd = server_ctx.working_dir();
                let this_cell = cells.get(cells.find(cwd)?).unwrap();

                let mappings: IndexMap<_, _> = {
                    if self.aliases_to_resolve.is_empty() {
                        if self.aliases {
                            this_cell
                                .cell_alias_resolver()
                                .mappings()
                                .map(|(alias, cell_name)| {
                                    (
                                        alias.to_string(),
                                        fs.resolve(
                                            cells
                                                .get(cell_name)
                                                .unwrap()
                                                .path()
                                                .as_project_relative_path(),
                                        ),
                                    )
                                })
                                .collect()
                        } else {
                            cells
                                .cells()
                                .map(|(name, cell)| {
                                    (
                                        name.as_str().to_owned(),
                                        fs.resolve(cell.path().as_project_relative_path()),
                                    )
                                })
                                .collect()
                        }
                    } else {
                        let cell_alias_resolver = this_cell.cell_alias_resolver();
                        self.aliases_to_resolve
                            .iter()
                            .map(|alias| {
                                Ok((
                                    alias.to_owned(),
                                    fs.resolve(
                                        cells
                                            .get(cell_alias_resolver.resolve(alias)?)
                                            .unwrap()
                                            .path()
                                            .as_project_relative_path(),
                                    ),
                                ))
                            })
                            .collect::<anyhow::Result<_>>()?
                    }
                };

                let mut stdout = stdout.as_writer();
                if self.paths_only {
                    if self.json {
                        let paths: Vec<_> = mappings.values().collect();
                        writeln!(stdout, "{}", serde_json::to_string_pretty(&paths)?)?;
                    } else {
                        for v in mappings.values() {
                            writeln!(stdout, "{}", v)?;
                        }
                    }
                } else if self.json {
                    writeln!(stdout, "{}", serde_json::to_string_pretty(&mappings)?)?;
                } else {
                    for (k, v) in mappings {
                        writeln!(stdout, "{}: {}", k, v)?;
                    }
                }

                Ok(())
            })
            .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_audit::classpath::AuditClasspathCommand;
use buck2_build_api::actions::artifact::get_artifact_fs::GetArtifactFs;
use buck2_build_api::configure_targets::load_compatible_patterns;
use buck2_build_api::query::analysis::CLASSPATH_FOR_TARGETS;
use buck2_cli_proto::ClientContext;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_node::load_patterns::MissingTargetBehavior;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use buck2_server_ctx::pattern::parse_patterns_from_cli_args;
use buck2_server_ctx::pattern::target_platform_from_client_context;
use dupe::Dupe;
use gazebo::prelude::SliceExt;
use indexmap::IndexMap;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for AuditClasspathCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |server_ctx, mut ctx| {
                let cwd = server_ctx.working_dir();
                let parsed_patterns = parse_patterns_from_cli_args::<TargetPatternExtra>(
                    &mut ctx,
                    &self
                        .patterns
                        .map(|pat| buck2_data::TargetPattern { value: pat.clone() }),
                    cwd,
                )
                .await?;
                let target_platform =
                    target_platform_from_client_context(&client_ctx, server_ctx, &ctx).await?;
                // Incompatible targets are skipped because this is an audit command
                let targets = load_compatible_patterns(
                    &ctx,
                    parsed_patterns,
                    target_platform,
                    MissingTargetBehavior::Fail,
                )
                .await?;

                let mut stdout = stdout.as_writer();
                let artifact_fs = ctx.get_artifact_fs().await?;

                // Json prints a map of targets to list of classpaths while default prints
                // classpaths for all targets.
                if self.json {
                    let target_to_artifacts =
                        futures::future::try_join_all(targets.into_iter().map(|target| {
                            let ctx = &ctx;
                            async move {
                                let label = target.label().dupe();
                                let label_to_artifact =
                                    (CLASSPATH_FOR_TARGETS.get()?)(ctx, vec![label.dupe()]).await?;
                                anyhow::Ok((label, label_to_artifact))
                            }
                        }))
                        .await?;
                    let target_to_classpaths: anyhow::Result<IndexMap<_, _>> = target_to_artifacts
                        .into_iter()
                        .map(|(target, label_to_artifact)| {
                            let classpaths: anyhow::Result<Vec<_>> = label_to_artifact
                                .into_values()
                                .map(|artifact| {
                                    let path = artifact.get_path().resolve(&artifact_fs)?;
                                    anyhow::Ok(artifact_fs.fs().resolve(&path))
                                })
                                .collect();
                            // Note: We are choosing unconfigured targets here to match buck1 behavior.
                            // This means that if same unconfigured target with different configurations are audited,
                            // one will override the other in the output.
                            // The replacement for this command in the future should return configured targets.
                            anyhow::Ok((target.unconfigured().dupe(), classpaths?))
                        })
                        .collect();
                    writeln!(
                        stdout,
                        "{}",
                        serde_json::to_string_pretty(&target_to_classpaths?)?
                    )?;
                } else {
                    let label_to_artifact = (CLASSPATH_FOR_TARGETS.get()?)(
                        &ctx,
                        targets.into_iter().map(|t| t.label().dupe()).collect(),
                    )
                    .await?;
                    for (_label, artifact) in label_to_artifact {
                        let path = artifact.get_path().resolve(&artifact_fs)?;
                        let abs_path = artifact_fs.fs().resolve(&path);
                        writeln!(stdout, "{}", &abs_path)?;
                    }
                }

                Ok(())
            })
            .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::HashMap;
use std::io::Write;

use async_trait::async_trait;
use buck2_audit::config::AuditConfigCommand;
use buck2_audit::config::LocationStyle;
use buck2_audit::config::OutputFormat;
use buck2_audit::config::ValueStyle;
use buck2_cli_proto::ClientContext;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::legacy_configs::dice::HasLegacyConfigs;
use buck2_common::legacy_configs::LegacyBuckConfigLocation;
use buck2_common::legacy_configs::LegacyBuckConfigValue;
use buck2_core::cells::name::CellName;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use gazebo::prelude::*;
use serde_json::json;

use crate::AuditSubcommand;

fn print_location_string(
    writer: &mut impl Write,
    location: &LegacyBuckConfigLocation,
    keyword: &str,
) -> anyhow::Result<()> {
    match location {
        LegacyBuckConfigLocation::File(file, line) => {
            writeln!(writer, "  ({} at {}:{})", keyword, file, line)?;
        }
        LegacyBuckConfigLocation::CommandLineArgument => {
            writeln!(writer, "  ({} on the command line)", keyword)?;
        }
    }

    Ok(())
}

fn print_location(
    writer: &mut impl Write,
    value: &LegacyBuckConfigValue,
    style: LocationStyle,
) -> anyhow::Result<()> {
    match style {
        LocationStyle::None => {}
        LocationStyle::Direct => {
            let location = value.location();
            print_location_string(writer, &location, "defined")?;
        }
        LocationStyle::Extended => {
            let stack = value.location_stack();
            let mut iter = stack.iter();
            if let Some(location) = iter.next() {
                // Extra space in the keyword as to align "defined" and "included"
                print_location_string(writer, location, "defined ")?;
            }
            for location in iter {
                print_location_string(writer, location, "included")?;
            }
        }
    }

    Ok(())
}

fn print_value(
    writer: &mut impl Write,
    key: &str,
    value: &LegacyBuckConfigValue,
    style: ValueStyle,
) -> anyhow::Result<()> {
    match style {
        ValueStyle::Resolved => {
            writeln!(writer, "    {} = {}", key, value.as_str())?;
        }
        ValueStyle::Raw => {
            writeln!(writer, "    {} = {}", key, value.raw_value())?;
        }
        ValueStyle::Both => {
            writeln!(
                writer,
                "    {} = {}\n        (raw {})",
                key,
                value.as_str(),
                value.raw_value()
            )?;
        }
    }

    Ok(())
}

#[async_trait]
impl AuditSubcommand for AuditConfigCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        _client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |server_ctx, ctx| {
                let cwd = server_ctx.working_dir();
                let cell_resolver = ctx.get_cell_resolver().await?;

                let working_dir_cell = cell_resolver.find(cwd)?;
                let cell_alias_resolver = cell_resolver
                    .get(working_dir_cell)
                    .unwrap()
                    .cell_alias_resolver();

                let relevant_cell = match &self.cell {
                    Some(v) => v,
                    None => "",
                };

                let resolved_relevant_cell = cell_alias_resolver.resolve(relevant_cell)?;

                let config = ctx.get_legacy_configs().await?;

                let specs = self.specs.try_map(|v| {
                    let (cell, config) = match v.split_once("//") {
                        Some((cell, config)) => (cell_alias_resolver.resolve(cell)?, config),
                        None => (resolved_relevant_cell, v.as_str()),
                    };
                    let (section, key) = config.split1(".");
                    anyhow::Ok((cell, section, key, v))
                })?;

                let filter = move |cell: CellName, section: &str, key: &str| {
                    if specs.is_empty() {
                        if cell == resolved_relevant_cell {
                            Some(format!("{}.{}", section, key))
                        } else {
                            None
                        }
                    } else {
                        for (filter_cell, filter_section, filter_key, spec) in &specs {
                            if cell == *filter_cell
                                && &section == filter_section
                                && (filter_key == &"" || &key == filter_key)
                            {
                                return if filter_key == &"" {
                                    Some(format!("{}.{}", section, key))
                                } else {
                                    Some((*spec).to_owned())
                                };
                            }
                        }
                        None
                    }
                };

                let mut stdout = stdout.as_writer();

                match self.output_format() {
                    OutputFormat::Json => writeln!(
                        &mut stdout,
                        "{}",
                        json!(
                            config
                                .iter()
                                .flat_map(|(cell, cell_config)| cell_config
                                    .all_sections()
                                    .map(move |(section, cfg)| (cell, section, cfg)))
                                .flat_map(|(cell, section, cfg)| {
                                    cfg.iter()
                                        .filter_map(|(key, value)| {
                                            filter(cell, section, key)
                                                .map(|spec| (spec, value.as_str().to_owned()))
                                        })
                                        .collect::<HashMap<String, String>>()
                                })
                                .collect::<HashMap<String, String>>()
                        )
                    )?,
                    OutputFormat::Simple => {
                        for (cell, cell_config) in config.iter() {
                            for section in cell_config.sections() {
                                let mut printed_section = false;
                                let values = cell_config.get_section(section).unwrap();
                                for (key, value) in values.iter() {
                                    if filter(cell, section, key).is_some() {
                                        if !printed_section {
                                            writeln!(&mut stdout, "[{}]", section)?;
                                            printed_section = true;
                                        }
                                        print_value(&mut stdout, key, &value, self.value_style)?;
                                        print_location(&mut stdout, &value, self.location_style)?;
                                    }
                                }
                            }
                        }
                    }
                }

                Ok(())
            })
            .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_audit::configurations::AuditConfigurationsCommand;
use buck2_cli_proto::ClientContext;
use buck2_core::configuration::bound_id::BoundConfigurationId;
use buck2_core::configuration::data::ConfigurationData;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use itertools::Itertools;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for AuditConfigurationsCommand {
    async fn server_execute(
        &self,
        _server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        _client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        let mut stdout = stdout.as_writer();

        if self.configs.is_empty() {
            for cfg in ConfigurationData::iter_existing()
                .filter(|c| c.is_bound())
                .sorted_by_cached_key(|c| c.full_name().to_owned())
            {
                print_cfg(&mut stdout, &cfg)?;
            }
        } else {
            for cfg in &self.configs {
                let cfg = BoundConfigurationId::parse(cfg)?;
                let cfg = ConfigurationData::lookup_bound(cfg)?;
                print_cfg(&mut stdout, &cfg)?;
            }
        }

        Ok(())
    }
}

fn print_cfg(stdout: &mut impl Write, cfg: &ConfigurationData) -> anyhow::Result<()> {
    writeln!(stdout, "{}:", cfg.full_name())?;
    let data = cfg.data()?;
    for (constraint_key, constraint_value) in data
        .constraints
        .iter()
        .sorted_by(|(k1, v1), (k2, v2)| (v1, k1).cmp(&(v2, k2)))
    {
        // to_string() on the value is required to get the format string behavior.
        writeln!(
            stdout,
            "  {:<59} ({})",
            constraint_value.to_string(),
            constraint_key
        )?;
    }

    Ok(())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use anyhow::Context;
use async_trait::async_trait;
use buck2_audit::deferred_materializer::DeferredMaterializerCommand;
use buck2_audit::deferred_materializer::DeferredMaterializerSubcommand;
use buck2_cli_proto::ClientContext;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use futures::stream::StreamExt;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for DeferredMaterializerCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        _client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        let mut stdout = stdout.as_writer();

        let materializer = server_ctx.materializer();
        let deferred_materializer = materializer
            .as_deferred_materializer_extension()
            .context("Deferred materializer is not in use")?;

        match self.subcommand {
            DeferredMaterializerSubcommand::List => {
                let mut stream = deferred_materializer
                    .iterate()
                    .context("Failed to start iterating")?;

                while let Some((path, entry)) = stream.next().await {
                    writeln!(stdout, "{}\t{}", path, entry)?;
                }
            }
            DeferredMaterializerSubcommand::Fsck => {
                let mut stream = deferred_materializer
                    .fsck()
                    .context("Failed to start iterating")?;

                let mut n = 0;

                while let Some((path, error)) = stream.next().await {
                    n += 1;
                    writeln!(stdout, "{}\t{:#}", path, error)?;
                }

                let mut stderr = server_ctx.stderr()?;
                writeln!(&mut stderr, "total errors: {}", n)?;
            }
            DeferredMaterializerSubcommand::Refresh { min_ttl } => {
                deferred_materializer
                    .refresh_ttls(min_ttl)
                    .await
                    .context("Failed to refresh")?;
            }
            DeferredMaterializerSubcommand::GetRefreshLog => {
                let text = deferred_materializer
                    .get_ttl_refresh_log()
                    .await
                    .context("Failed to get_ttl_refresh_log")?;

                write!(stdout, "{}", text)?;
            }
            DeferredMaterializerSubcommand::TestIter { count } => {
                let text = deferred_materializer
                    .test_iter(count)
                    .await
                    .context("Failed to test_iter")?;

                write!(stdout, "{}", text)?;
            }
        }

        anyhow::Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use anyhow::Context as _;
use async_trait::async_trait;
use buck2_audit::dep_files::AuditDepFilesCommand;
use buck2_build_api::audit_dep_files::AUDIT_DEP_FILES;
use buck2_cli_proto::ClientContext;
use buck2_core::category::Category;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_node::target_calculation::ConfiguredTargetCalculation;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use buck2_server_ctx::pattern::parse_patterns_from_cli_args;
use buck2_server_ctx::pattern::target_platform_from_client_context;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for AuditDepFilesCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |server_ctx, mut ctx| {
                let target_platform =
                    target_platform_from_client_context(&client_ctx, server_ctx, &ctx).await?;

                let label = parse_patterns_from_cli_args::<TargetPatternExtra>(
                    &mut ctx,
                    &[buck2_data::TargetPattern {
                        value: self.pattern.clone(),
                    }],
                    server_ctx.working_dir(),
                )
                .await?
                .into_iter()
                .next()
                .context("Parsing patterns returned nothing")?
                .as_target_label(&self.pattern)?;

                let label = ctx
                    .get_configured_target(&label, target_platform.as_ref())
                    .await?;

                let category = Category::try_from(self.category.as_str())?;

                (AUDIT_DEP_FILES.get()?)(
                    &ctx,
                    label,
                    category,
                    self.identifier.clone(),
                    &mut stdout.as_writer(),
                )
                .await?;

                Ok(())
            })
            .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_audit::execution_platform_resolution::AuditExecutionPlatformResolutionCommand;
use buck2_cli_proto::ClientContext;
use buck2_core::configuration::bound_id::BoundConfigurationId;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::pattern::pattern_type::ConfigurationPredicate;
use buck2_core::pattern::pattern_type::ConfiguredTargetPatternExtra;
use buck2_core::pattern::ParsedPattern;
use buck2_core::target::label::TargetLabel;
use buck2_node::load_patterns::load_patterns;
use buck2_node::load_patterns::MissingTargetBehavior;
use buck2_node::nodes::configured_frontend::ConfiguredTargetNodeCalculation;
use buck2_node::target_calculation::ConfiguredTargetCalculation;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use buck2_server_ctx::pattern::target_platform_from_client_context;
use buck2_server_ctx::pattern::PatternParser;
use indent_write::io::IndentWriter;

use crate::AuditSubcommand;

#[derive(Debug, thiserror::Error)]
enum AuditExecutionPlatformResolutionCommandError {
    #[error("Builtin configurations are not supported: `{0}`")]
    BuiltinConfigurationsNotSupported(String),
    #[error(
        "Patterns with configuration label without configuration hash are not supported: `{0}`"
    )]
    ConfigurationLabelWithoutHashNotSupported(String),
}

#[async_trait]
impl AuditSubcommand for AuditExecutionPlatformResolutionCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx.with_dice_ctx(
            async move |server_ctx, mut ctx| {
                let pattern_parser = PatternParser::new(
                    &mut ctx,
                    server_ctx.working_dir(),
                ).await?;

                let mut configured_patterns = Vec::new();
                let mut target_patterns = Vec::new();
                for pat in self.patterns.iter() {
                    let pat = pattern_parser.parse_pattern::<ConfiguredTargetPatternExtra>(pat)?;
                    match pat.clone() {
                        ParsedPattern::Package(pkg) => target_patterns.push(ParsedPattern::Package(pkg)),
                        ParsedPattern::Recursive(path) => target_patterns.push(ParsedPattern::Recursive(path)),
                        ParsedPattern::Target(pkg, target_name, extra) => {
                            match extra.cfg {
                                ConfigurationPredicate::Any => target_patterns.push(ParsedPattern::Target(pkg, target_name, extra)),
                                ConfigurationPredicate::Builtin(_) => return Err(AuditExecutionPlatformResolutionCommandError::BuiltinConfigurationsNotSupported(pat.to_string()).into()),
                                ConfigurationPredicate::Bound(_label, None) => return Err(AuditExecutionPlatformResolutionCommandError::ConfigurationLabelWithoutHashNotSupported(pat.to_string()).into()),
                                ConfigurationPredicate::Bound(label, Some(hash)) => {
                                    let cfg = ConfigurationData::lookup_bound(BoundConfigurationId { label, hash })?;
                                    configured_patterns.push(TargetLabel::new(pkg, target_name.as_ref()).configure(cfg));
                                }
                            }
                        }
                    }
                }

                let loaded_patterns = load_patterns(&ctx, target_patterns, MissingTargetBehavior::Fail).await?;
                let target_platform = target_platform_from_client_context(
                    &client_ctx,
                    server_ctx,
                    &ctx,
                )
                .await?;

                for (_, targets) in loaded_patterns.into_iter() {
                    for (_, node) in targets? {
                        configured_patterns.push(
                            ctx.get_configured_target(node.label(), target_platform.as_ref())
                                .await?,
                        );
                    }
                }

                let mut stdout = stdout.as_writer();

                for configured_target in configured_patterns {
                    let configured_node = ctx.get_configured_target_node(&configured_target).await?;
                    let configured_node = configured_node.require_compatible()?;
                    writeln!(stdout, "{}:", configured_target)?;
                    let resolution = configured_node.execution_platform_resolution();
                    match resolution.platform() {
                        Ok(platform) => {
                            writeln!(stdout, "  Execution platform: {}", platform.id())?;
                            writeln!(stdout, "    Execution platform configuration: {}", platform.cfg())?;
                            writeln!(stdout, "    Execution deps:")?;
                            for execution_dep in configured_node.exec_deps() {
                                writeln!(stdout, "      {}", execution_dep.label())?;
                            }
                            writeln!(stdout, "    Toolchain deps:")?;
                            for toolchain_dep in configured_node.toolchain_deps() {
                                writeln!(stdout, "      {}", toolchain_dep.label())?;
                            }
                            for (label, reason) in resolution.skipped() {
                                writeln!(stdout, "    Skipped {}", label)?;
                                writeln!(IndentWriter::new("      ", &mut stdout), "{:#}", reason)?;
                            }
                        }
                        Err(e) => writeln!(stdout, "{}", e)?,
                    }
                }

                Ok(())
            })
        .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_audit::includes::AuditIncludesCommand;
use buck2_cli_proto::ClientContext;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::result::SharedResult;
use buck2_common::result::ToSharedResultExt;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::CellResolver;
use buck2_core::fs::fs_util;
use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::paths::file_name::FileNameBuf;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::package::PackageLabel;
use buck2_interpreter::file_loader::LoadedModule;
use buck2_interpreter::load_module::InterpreterCalculation;
use buck2_interpreter::path::StarlarkModulePath;
use buck2_node::nodes::eval_result::EvaluationResult;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use buck2_query::query::environment::LabeledNode;
use buck2_query::query::traversal::async_depth_first_postorder_traversal;
use buck2_query::query::traversal::AsyncNodeLookup;
use buck2_query::query::traversal::AsyncTraversalDelegate;
use buck2_query::query::traversal::ChildVisitor;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use derive_more::Display;
use dice::DiceComputations;
use dupe::Dupe;
use futures::stream::FuturesOrdered;
use futures::StreamExt;
use gazebo::prelude::*;
use indexmap::indexmap;
use itertools::Itertools;
use ref_cast::RefCast;
use serde::ser::SerializeMap;
use serde::Serialize;
use serde::Serializer;
use thiserror::Error;

use crate::AuditSubcommand;

#[derive(Debug, Error)]
enum AuditIncludesError {
    #[error("Error loading buildfile for `{0}` found a mismatched buildfile name (`{1}`)")]
    WrongBuildfilePath(CellPath, FileNameBuf),
    #[error("invalid buildfile path `{0}`")]
    InvalidPath(CellPath),
}

async fn get_transitive_includes(
    ctx: &DiceComputations,
    load_result: &EvaluationResult,
) -> anyhow::Result<Vec<ImportPath>> {
    // We define a simple graph of LoadedModules to traverse.
    #[derive(Clone, Dupe)]
    struct Node(LoadedModule);

    impl Node {
        fn import_path(&self) -> &ImportPath {
            self.0
                .path()
                .unpack_load_file()
                .expect("only visit imports so only bzl files are expected")
        }
    }

    #[derive(Display, Debug, Hash, Eq, PartialEq, Clone, RefCast)]
    #[repr(transparent)]
    struct NodeRef(ImportPath);

    impl LabeledNode for Node {
        type NodeRef = NodeRef;

        fn node_ref(&self) -> &NodeRef {
            NodeRef::ref_cast(self.import_path())
        }
    }

    struct Lookup<'a> {
        ctx: &'a DiceComputations,
    }

    #[async_trait]
    impl AsyncNodeLookup<Node> for Lookup<'_> {
        async fn get(&self, label: &NodeRef) -> anyhow::Result<Node> {
            Ok(Node(
                self.ctx
                    .get_loaded_module(StarlarkModulePath::LoadFile(&label.0))
                    .await?,
            ))
        }
    }

    struct Delegate {
        imports: Vec<ImportPath>,
    }

    #[async_trait]
    impl AsyncTraversalDelegate<Node> for Delegate {
        fn visit(&mut self, target: Node) -> anyhow::Result<()> {
            self.imports.push(target.import_path().clone());
            Ok(())
        }

        async fn for_each_child(
            &mut self,
            target: &Node,
            func: &mut dyn ChildVisitor<Node>,
        ) -> anyhow::Result<()> {
            for import in target.0.imports() {
                func.visit(NodeRef(import.clone()))?;
            }
            Ok(())
        }
    }

    let mut delegate = Delegate { imports: vec![] };
    let lookup = Lookup { ctx };

    async_depth_first_postorder_traversal(
        &lookup,
        load_result.imports().map(NodeRef::ref_cast),
        &mut delegate,
    )
    .await?;
    Ok(delegate.imports)
}

async fn load_and_collect_includes(
    ctx: &DiceComputations,
    path: &CellPath,
) -> SharedResult<Vec<ImportPath>> {
    let parent = path
        .parent()
        .ok_or_else(|| anyhow::anyhow!(AuditIncludesError::InvalidPath(path.clone())))?;
    let package = PackageLabel::from_cell_path(parent);
    let load_result = ctx.get_interpreter_results(package).await?;

    let buildfile_name = load_result.buildfile_path().filename();
    if buildfile_name
        != path
            .path()
            .file_name()
            .expect("checked that this has a parent above")
    {
        return Err(anyhow::anyhow!(AuditIncludesError::WrongBuildfilePath(
            path.clone(),
            buildfile_name.to_owned(),
        )))
        .shared_error();
    }

    Ok(get_transitive_includes(ctx, &load_result).await?)
}

fn resolve_path(
    cells: &CellResolver,
    fs: &ProjectRoot,
    current_cell_abs_path: &AbsNormPath,
    path: &str,
) -> anyhow::Result<CellPath> {
    // To match buck1, if the path is absolute we use it as-is, but if not it is treated
    // as relative to the working dir cell root (not the working dir).
    // The easiest way to consistently handle non-canonical paths
    // is to just resolve to absolute here, and then relativize.
    //
    // Note if the path is already absolute, this operation is a no-op.
    let path = current_cell_abs_path.as_abs_path().join(path);

    let abs_path = fs_util::canonicalize(path)?;

    let project_path = fs.relativize(&abs_path)?;
    cells.get_cell_path(&project_path)
}

#[async_trait]
impl AuditSubcommand for AuditIncludesCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        _client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |server_ctx, ctx| {
                let cells = ctx.get_cell_resolver().await?;
                let cwd = server_ctx.working_dir();
                let current_cell = cells.get(cells.find(cwd)?)?;
                let fs = server_ctx.project_root();
                let current_cell_abs_path =
                    fs.resolve(current_cell.path().as_project_relative_path());

                let futures: FuturesOrdered<_> = self
                    .patterns
                    .iter()
                    .unique()
                    .map(|path| {
                        let path = path.to_owned();
                        let ctx = ctx.dupe();
                        let cell_path = resolve_path(&cells, fs, &current_cell_abs_path, &path);
                        async move {
                            let load_result = try {
                                let cell_path = cell_path?;
                                load_and_collect_includes(&ctx, &cell_path).await?
                            };
                            (path, load_result)
                        }
                    })
                    .collect();

                let results: Vec<(_, SharedResult<Vec<_>>)> = futures.collect().await;
                // This is expected to not return any errors, and so we're not careful about not propagating it.
                let to_absolute_path = move |include: ImportPath| -> anyhow::Result<_> {
                    let include = include.path();
                    let cell = cells.get(include.cell())?;
                    let path = cell.path().join(include.path());
                    Ok(fs.resolve(&path))
                };
                let absolutize_paths =
                    |paths: Vec<ImportPath>| -> SharedResult<Vec<AbsNormPathBuf>> {
                        Ok(paths.into_try_map(&to_absolute_path)?)
                    };
                let results: Vec<(String, SharedResult<Vec<AbsNormPathBuf>>)> = results
                    .into_map(|(path, includes)| (path, includes.and_then(absolutize_paths)));

                let mut stdout = stdout.as_writer();

                // For the printing of results, we don't need to propagate errors, just print
                // them. After we print the results, we'll propagate an error if there is one.
                if self.json {
                    let mut ser = serde_json::Serializer::pretty(&mut stdout);
                    // buck1 has a bug where it doesn't properly handle >1 arg when passed --json
                    // it also, sadly, prints just a single list of outputs for that case. we match
                    // buck1's behavior for 1 successful file and print a dictionary for multiple. This is
                    // unfortunate, but we hope that users can migrate to the equivalent query commands instead.
                    if let Some((_path, Ok(includes))) = results.as_singleton() {
                        includes.serialize(&mut ser)?
                    } else {
                        let mut map = ser.serialize_map(Some(results.len()))?;
                        for (path, includes) in &results {
                            match includes {
                                Ok(includes) => {
                                    map.serialize_entry(path, &indexmap! {"includes" => &includes})?
                                }
                                Err(e) => map.serialize_entry(
                                    path,
                                    &indexmap! {"$error" => format!("{:#}", e)},
                                )?,
                            }
                        }
                        map.end()?;
                    }

                    // flush a newline after serde output.
                    writeln!(stdout)?;
                } else {
                    for (path, includes) in &results {
                        match includes {
                            Ok(includes) => {
                                // intentionally add a blank line after the header
                                writeln!(stdout, "# {}\n", path)?;
                                for include in includes {
                                    // To match buck1, we print absolute paths.
                                    writeln!(stdout, "{}", include)?;
                                }
                            }
                            Err(e) => {
                                // intentionally add a blank line after the header
                                writeln!(stdout, "! {}\n", path)?;
                                writeln!(stdout, "{:#}", e)?;
                            }
                        }
                    }
                }

                // propagate the first error.
                for (_, result) in results {
                    result?;
                }

                Ok(())
            })
            .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

#![feature(async_closure)]
#![feature(try_blocks)]
#![feature(provide_any)]

use std::sync::Once;

use async_trait::async_trait;
use buck2_audit::AuditCommand;
use buck2_cli_proto::ClientContext;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;

mod analysis_queries;
mod cell;
mod classpath;
mod config;
mod configurations;
pub mod deferred_materializer;
mod dep_files;
mod execution_platform_resolution;
mod includes;
pub mod output;
mod prelude;
mod providers;
pub mod server;
mod starlark;
mod visibility;

/// `buck2 audit` subcommands have a somewhat unique approach to make it really easy to
/// add them without the boilerplate necessary for normal commands. The main difference
/// is that there is not a custom endpoint added in the daemon for each subcommand, instead
/// there is a single endpoint where we send the entire serialized AuditCommand and
/// reproduce it in the daemon.
///
/// Audit subcommands implement this trait so that we can handle the entire client side
/// logic here and to support that serialization to the daemon.
#[async_trait]
pub trait AuditSubcommand: Send + Sync + 'static {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_server_ctx: ClientContext,
    ) -> anyhow::Result<()>;
}

#[async_trait]
pub trait AuditCommandExt {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_server_ctx: ClientContext,
    ) -> anyhow::Result<()>;
    fn as_subcommand(&self) -> &dyn AuditSubcommand;
}

#[async_trait]
impl AuditCommandExt for AuditCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_server_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        self.as_subcommand()
            .server_execute(server_ctx, stdout, client_server_ctx)
            .await
    }
    fn as_subcommand(&self) -> &dyn AuditSubcommand {
        match self {
            AuditCommand::Cell(cmd) => cmd,
            AuditCommand::Classpath(cmd) => cmd,
            AuditCommand::Config(cmd) => cmd,
            AuditCommand::Configurations(cmd) => cmd,
            AuditCommand::Includes(cmd) => cmd,
            AuditCommand::Prelude(cmd) => cmd,
            AuditCommand::Providers(cmd) => cmd,
            AuditCommand::AnalysisQueries(cmd) => cmd,
            AuditCommand::ExecutionPlatformResolution(cmd) => cmd,
            AuditCommand::Starlark(cmd) => cmd,
            AuditCommand::DepFiles(cmd) => cmd,
            AuditCommand::DeferredMaterializer(cmd) => cmd,
            AuditCommand::Visibility(cmd) => cmd,
            AuditCommand::Output(cmd) => cmd,
            AuditCommand::Parse(cmd) => cmd,
        }
    }
}

pub fn init_late_bindings() {
    static ONCE: Once = Once::new();
    ONCE.call_once(output::command::init_audit_output)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_audit::prelude::AuditPreludeCommand;
use buck2_cli_proto::ClientContext;
use buck2_common::dice::cells::HasCellResolver;
use buck2_interpreter::file_type::StarlarkFileType;
use buck2_interpreter::load_module::InterpreterCalculation;
use buck2_interpreter::load_module::INTERPRETER_CALCULATION_IMPL;
use buck2_interpreter::prelude_path::prelude_path;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for AuditPreludeCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        _client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |_server_ctx, ctx| {
                let mut stdout = stdout.as_writer();
                // Print out all the Prelude-like stuff that is loaded into each module
                let cell_resolver = ctx.get_cell_resolver().await?;
                let prelude_path = prelude_path(&cell_resolver)?;
                writeln!(
                    stdout,
                    "{}",
                    INTERPRETER_CALCULATION_IMPL
                        .get()?
                        .global_env_for_file_type(&ctx, StarlarkFileType::Buck)
                        .await?
                        .describe()
                )?;
                writeln!(
                    stdout,
                    "{}",
                    ctx.get_loaded_module_from_import_path(prelude_path.import_path())
                        .await?
                        .env()
                        .describe()
                )?;

                Ok(())
            })
            .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Write;

use async_trait::async_trait;
use buck2_audit::providers::AuditProvidersCommand;
use buck2_build_api::analysis::calculation::RuleAnalysisCalculation;
use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollectionValue;
use buck2_cli_proto::ClientContext;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::dice::file_ops::HasFileOps;
use buck2_common::pattern::resolve::resolve_target_patterns;
use buck2_core::pattern::pattern_type::ProvidersPatternExtra;
use buck2_core::provider::label::ProvidersName;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use buck2_node::target_calculation::ConfiguredTargetCalculation;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use buck2_server_ctx::pattern::parse_patterns_from_cli_args;
use buck2_server_ctx::pattern::target_platform_from_client_context;
use buck2_util::indent::indent;
use dice::DiceTransaction;
use dupe::Dupe;
use futures::stream::FuturesOrdered;
use futures::StreamExt;
use gazebo::prelude::*;

use crate::AuditSubcommand;

#[async_trait]
impl AuditSubcommand for AuditProvidersCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(move |server_ctx, ctx| {
                server_execute_with_dice(self, client_ctx, server_ctx, stdout, ctx)
            })
            .await
    }
}

#[derive(Debug, thiserror::Error)]
enum AuditProvidersError {
    #[error("Evaluation of at least one target providers failed")]
    AtLeastOneFailed,
}

async fn server_execute_with_dice(
    command: &AuditProvidersCommand,
    client_ctx: ClientContext,
    server_ctx: &dyn ServerCommandContextTrait,
    mut stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
    mut ctx: DiceTransaction,
) -> anyhow::Result<()> {
    let cells = ctx.get_cell_resolver().await?;
    let target_platform =
        target_platform_from_client_context(&client_ctx, server_ctx, &ctx).await?;

    let parsed_patterns = parse_patterns_from_cli_args::<ProvidersPatternExtra>(
        &mut ctx,
        &command
            .patterns
            .map(|pat| buck2_data::TargetPattern { value: pat.clone() }),
        server_ctx.working_dir(),
    )
    .await?;
    let resolved_pattern =
        resolve_target_patterns(&cells, &parsed_patterns, &ctx.file_ops()).await?;

    let mut futs = FuturesOrdered::new();
    for (package, spec) in resolved_pattern.specs {
        let ctx = &ctx;
        let targets = match spec {
            buck2_core::pattern::PackageSpec::Targets(targets) => targets,
            buck2_core::pattern::PackageSpec::All => {
                let interpreter_results = ctx.get_interpreter_results(package.dupe()).await?;
                interpreter_results
                    .targets()
                    .keys()
                    .map(|target| {
                        (
                            target.to_owned(),
                            ProvidersPatternExtra {
                                providers: ProvidersName::Default,
                            },
                        )
                    })
                    .collect()
            }
        };

        for (target_name, providers) in targets {
            let label = providers.into_providers_label(package.dupe(), target_name.as_ref());
            let providers_label = ctx
                .get_configured_provider_label(&label, target_platform.as_ref())
                .await?;

            // `.push` is deprecated in newer `futures`,
            // but we did not updated vendored `futures` yet.
            #[allow(deprecated)]
            futs.push(async move {
                let result = ctx.get_providers(&providers_label).await;
                (providers_label, result)
            });
        }
    }

    let mut stdout = stdout.as_writer();
    let mut stderr = server_ctx.stderr()?;

    let mut at_least_one_error = false;
    while let Some((target, result)) = futs.next().await {
        match result {
            Ok(v) => {
                let v: FrozenProviderCollectionValue = v.require_compatible()?;

                if command.quiet {
                    writeln!(&mut stdout, "{}", target)?
                } else if command.list {
                    let mut provider_names = v.provider_collection().provider_names();
                    // Create a deterministic output.
                    provider_names.sort();
                    write!(
                        &mut stdout,
                        "{}:\n{}",
                        target,
                        indent(
                            "  ",
                            &provider_names
                                .iter()
                                .fold(String::new(), |acc, arg| acc + &format!("- {}\n", arg))
                        )
                    )?;
                } else if command.print_debug {
                    write!(
                        &mut stdout,
                        "{}:\n{}",
                        target,
                        indent("  ", &format!("{:?}", v.provider_collection()))
                    )?;
                } else {
                    write!(
                        &mut stdout,
                        "{}:\n{}",
                        target,
                        indent("  ", &format!("{:#}", v.provider_collection()))
                    )?;
                }
            }
            Err(e) => {
                write!(
                    &mut stderr,
                    "{}: failed:\n{}",
                    target,
                    indent("  ", &format!("{:?}", e))
                )?;
                at_least_one_error = true;
            }
        }
    }

    stdout.flush()?;
    stderr.flush()?;

    if at_least_one_error {
        Err(AuditProvidersError::AtLeastOneFailed.into())
    } else {
        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::HashMap;

use buck2_core::env_helper::EnvHelper;
use buck2_events::dispatch::instant_hg;
use buck2_events::dispatch::span_async;
use buck2_server_ctx::command_end::command_end;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;

use crate::AuditCommand;
use crate::AuditCommandExt;

pub async fn server_audit_command(
    ctx: &dyn ServerCommandContextTrait,
    partial_result_dispatcher: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
    req: buck2_cli_proto::GenericRequest,
) -> anyhow::Result<buck2_cli_proto::GenericResponse> {
    let metadata = ctx.request_metadata().await?;
    let start_event = buck2_data::CommandStart {
        metadata: metadata.clone(),
        data: Some(buck2_data::AuditCommandStart {}.into()),
    };

    // TODO pass in log setting thru ClientContext instead of env var (see D29824148)
    static LOG_REPRODUCE: EnvHelper<bool> = EnvHelper::new("LOG_REPRODUCE");
    if LOG_REPRODUCE.get_copied()?.unwrap_or(false) {
        instant_hg().await;
    }

    span_async(
        start_event,
        server_audit_command_inner(metadata, ctx, partial_result_dispatcher, req),
    )
    .await
}

async fn server_audit_command_inner(
    metadata: HashMap<String, String>,
    context: &dyn ServerCommandContextTrait,
    partial_result_dispatcher: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
    req: buck2_cli_proto::GenericRequest,
) -> (
    anyhow::Result<buck2_cli_proto::GenericResponse>,
    buck2_data::CommandEnd,
) {
    let args = req.serialized_opts.to_owned();
    let dir = context.working_dir().to_string();
    let result = parse_command_and_execute(context, partial_result_dispatcher, req).await;
    let end_event = command_end(
        metadata,
        &result,
        buck2_data::AuditCommandEnd {
            status: if result.is_ok() { 0 } else { 1 },
            args,
            dir,
        },
    );

    let result = result.map(|()| buck2_cli_proto::GenericResponse {});

    (result, end_event)
}

async fn parse_command_and_execute(
    context: &dyn ServerCommandContextTrait,
    partial_result_dispatcher: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
    req: buck2_cli_proto::GenericRequest,
) -> anyhow::Result<()> {
    let command: AuditCommand = serde_json::from_str(&req.serialized_opts)?;
    command
        .server_execute(
            context,
            partial_result_dispatcher,
            req.context.expect("buck cli always sets a client context"),
        )
        .await
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use async_trait::async_trait;
use buck2_audit::visibility::AuditVisibilityCommand;
use buck2_cli_proto::ClientContext;
use buck2_common::result::SharedResult;
use buck2_common::result::ToUnsharedResultExt;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_node::load_patterns::load_patterns;
use buck2_node::load_patterns::MissingTargetBehavior;
use buck2_node::nodes::lookup::TargetNodeLookup;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_node::visibility::VisibilityError;
use buck2_query::query::syntax::simple::eval::set::TargetSet;
use buck2_query::query::traversal::async_depth_first_postorder_traversal;
use buck2_query::query::traversal::AsyncTraversalDelegate;
use buck2_query::query::traversal::ChildVisitor;
use buck2_server_ctx::ctx::ServerCommandContextTrait;
use buck2_server_ctx::ctx::ServerCommandDiceContext;
use buck2_server_ctx::partial_result_dispatcher::PartialResultDispatcher;
use buck2_server_ctx::pattern::parse_patterns_from_cli_args;
use dice::DiceTransaction;
use dupe::Dupe;
use gazebo::prelude::SliceExt;

use crate::AuditSubcommand;

#[derive(thiserror::Error, Debug)]
enum VisibilityCommandError {
    #[error(
        "Internal Error: The dependency `{0}` of the target `{1}` was not found during the traversal."
    )]
    DepNodeNotFound(String, String),
}

async fn verify_visibility(
    ctx: DiceTransaction,
    targets: TargetSet<TargetNode>,
) -> anyhow::Result<()> {
    struct Delegate {
        targets: TargetSet<TargetNode>,
    }

    #[async_trait]
    impl AsyncTraversalDelegate<TargetNode> for Delegate {
        fn visit(&mut self, target: TargetNode) -> anyhow::Result<()> {
            self.targets.insert(target);
            Ok(())
        }
        async fn for_each_child(
            &mut self,
            target: &TargetNode,
            func: &mut dyn ChildVisitor<TargetNode>,
        ) -> anyhow::Result<()> {
            for dep in target.deps() {
                func.visit(dep.dupe())?;
            }
            Ok(())
        }
    }

    let lookup = TargetNodeLookup(&ctx);

    let mut delegate = Delegate {
        targets: TargetSet::<TargetNode>::new(),
    };

    async_depth_first_postorder_traversal(&lookup, targets.iter_names(), &mut delegate).await?;

    let mut visibility_errors = Vec::new();

    for target in delegate.targets.iter() {
        for dep in target.deps() {
            match delegate.targets.get(dep) {
                Some(val) => {
                    if !val.is_visible_to(target.label())? {
                        visibility_errors.push(VisibilityError::NotVisibleTo(
                            dep.dupe(),
                            target.label().dupe(),
                        ));
                    }
                }
                None => {
                    return Err(anyhow::Error::new(VisibilityCommandError::DepNodeNotFound(
                        dep.to_string(),
                        target.label().name().to_string(),
                    )));
                }
            }
        }
    }

    for err in &visibility_errors {
        buck2_client_ctx::eprintln!("{}", err)?;
    }

    if !visibility_errors.is_empty() {
        return Err(anyhow::anyhow!("{}", 1));
    }

    buck2_client_ctx::eprintln!("audit visibility succeeded")?;
    Ok(())
}

#[async_trait]
impl AuditSubcommand for AuditVisibilityCommand {
    async fn server_execute(
        &self,
        server_ctx: &dyn ServerCommandContextTrait,
        _stdout: PartialResultDispatcher<buck2_cli_proto::StdoutBytes>,
        _client_ctx: ClientContext,
    ) -> anyhow::Result<()> {
        server_ctx
            .with_dice_ctx(async move |server_ctx, mut ctx| {
                let parsed_patterns = parse_patterns_from_cli_args::<TargetPatternExtra>(
                    &mut ctx,
                    &self
                        .patterns
                        .map(|pat| buck2_data::TargetPattern { value: pat.clone() }),
                    server_ctx.working_dir(),
                )
                .await?;

                let parsed_target_patterns =
                    load_patterns(&ctx, parsed_patterns, MissingTargetBehavior::Fail).await?;

                let mut nodes = TargetSet::<TargetNode>::new();
                for (_package, result) in parsed_target_patterns.iter() {
                    match result {
                        Ok(res) => {
                            nodes.extend(res.values());
                        }
                        Err(e) => {
                            return SharedResult::unshared_error(Err(e.dupe()));
                        }
                    }
                }

                verify_visibility(ctx, nodes).await?;
                Ok(())
            })
            .await
    }
}