use anyhow::Context;
use codex_app_server_protocol::PluginAvailability;
use codex_app_server_protocol::PluginInstallPolicy;
use codex_login::CodexAuth;
use codex_plugin::PluginCapabilitySummary;
use std::collections::HashSet;
use tracing::warn;

use crate::PluginsConfigInput;
use crate::PluginsManager;
use crate::marketplace::MarketplacePluginInstallPolicy;
use crate::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;

const TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST: &[&str] = &[
    "github@openai-curated",
    "notion@openai-curated",
    "slack@openai-curated",
    "gmail@openai-curated",
    "google-calendar@openai-curated",
    "google-drive@openai-curated",
    "openai-developers@openai-curated",
    "canva@openai-curated",
    "teams@openai-curated",
    "sharepoint@openai-curated",
    "outlook-email@openai-curated",
    "outlook-calendar@openai-curated",
    "linear@openai-curated",
    "figma@openai-curated",
    "github@openai-curated-remote",
    "notion@openai-curated-remote",
    "slack@openai-curated-remote",
    "gmail@openai-curated-remote",
    "google-calendar@openai-curated-remote",
    "google-drive@openai-curated-remote",
    "openai-developers@openai-curated-remote",
    "canva@openai-curated-remote",
    "teams@openai-curated-remote",
    "sharepoint@openai-curated-remote",
    "outlook-email@openai-curated-remote",
    "outlook-calendar@openai-curated-remote",
    "linear@openai-curated-remote",
    "figma@openai-curated-remote",
    "chrome@openai-bundled",
    "computer-use@openai-bundled",
];

#[derive(Debug, Clone)]
pub struct ToolSuggestPluginDiscoveryInput {
    pub plugins: PluginsConfigInput,
    pub configured_plugin_ids: HashSet<String>,
    pub disabled_plugin_ids: HashSet<String>,
    pub loaded_plugin_app_connector_ids: HashSet<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ToolSuggestDiscoverablePlugin {
    pub id: String,
    pub remote_plugin_id: Option<String>,
    pub name: String,
    pub description: Option<String>,
    pub has_skills: bool,
    pub mcp_server_names: Vec<String>,
    pub app_connector_ids: Vec<String>,
}

impl PluginsManager {
    pub async fn list_tool_suggest_discoverable_plugins(
        &self,
        input: &ToolSuggestPluginDiscoveryInput,
        auth: Option<&CodexAuth>,
    ) -> anyhow::Result<Vec<ToolSuggestDiscoverablePlugin>> {
        if !input.plugins.plugins_enabled {
            return Ok(Vec::new());
        }

        let marketplaces = self
            .list_marketplaces_for_config(
                &input.plugins,
                &[],
                /*include_openai_curated*/ !input.plugins.remote_plugin_enabled,
            )
            .context("failed to list plugin marketplaces for tool suggestions")?
            .marketplaces;
        let remote_installed_marketplaces = if input.plugins.remote_plugin_enabled {
            self.build_remote_installed_plugin_marketplaces_from_cache(&[
                REMOTE_GLOBAL_MARKETPLACE_NAME,
            ])
        } else {
            None
        };

        let mut discoverable_plugins = Vec::<ToolSuggestDiscoverablePlugin>::new();
        for marketplace in marketplaces {
            let marketplace_name = marketplace.name;

            for plugin in marketplace.plugins {
                let is_configured_plugin = input.configured_plugin_ids.contains(plugin.id.as_str());
                let is_fallback_plugin =
                    TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST.contains(&plugin.id.as_str());
                if plugin.installed
                    || plugin.policy.installation == MarketplacePluginInstallPolicy::NotAvailable
                    || input.disabled_plugin_ids.contains(plugin.id.as_str())
                    || (!is_configured_plugin && !is_fallback_plugin)
                {
                    continue;
                }

                let plugin_id = plugin.id.clone();

                match self
                    .read_plugin_detail_for_marketplace_plugin(
                        &input.plugins,
                        &marketplace_name,
                        plugin,
                    )
                    .await
                {
                    Ok(plugin) => {
                        let plugin: PluginCapabilitySummary = plugin.into();
                        discoverable_plugins.push(ToolSuggestDiscoverablePlugin {
                            id: plugin.config_name,
                            remote_plugin_id: None,
                            name: plugin.display_name,
                            description: plugin.description,
                            has_skills: plugin.has_skills,
                            mcp_server_names: plugin.mcp_server_names,
                            app_connector_ids: plugin
                                .app_connector_ids
                                .into_iter()
                                .map(|connector_id| connector_id.0)
                                .collect(),
                        });
                    }
                    Err(err) => {
                        warn!("failed to load discoverable plugin suggestion {plugin_id}: {err:#}")
                    }
                }
            }
        }
        if let Some(remote_installed_marketplaces) = remote_installed_marketplaces.as_ref() {
            let mut installed_app_connector_ids = self
                .plugins_for_config(&input.plugins)
                .await
                .capability_summaries()
                .iter()
                .flat_map(|plugin| plugin.app_connector_ids.iter())
                .map(|connector_id| connector_id.0.clone())
                .collect::<HashSet<_>>();
            installed_app_connector_ids
                .extend(input.loaded_plugin_app_connector_ids.iter().cloned());
            let installed_remote_plugin_ids = remote_installed_marketplaces
                .iter()
                .flat_map(|marketplace| marketplace.plugins.iter())
                .map(|plugin| plugin.remote_plugin_id.clone())
                .collect::<HashSet<_>>();
            for plugin in
                self.cached_global_remote_discoverable_plugins_for_config(&input.plugins, auth)
            {
                let is_configured_plugin = input
                    .configured_plugin_ids
                    .contains(plugin.config_id.as_str())
                    || input
                        .configured_plugin_ids
                        .contains(plugin.remote_plugin_id.as_str());
                let is_fallback_plugin =
                    TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST.contains(&plugin.config_id.as_str());
                let matches_installed_app = plugin
                    .app_ids
                    .iter()
                    .any(|app_id| installed_app_connector_ids.contains(app_id.as_str()));
                let is_disabled = input
                    .disabled_plugin_ids
                    .contains(plugin.config_id.as_str())
                    || input
                        .disabled_plugin_ids
                        .contains(plugin.remote_plugin_id.as_str());
                if installed_remote_plugin_ids.contains(&plugin.remote_plugin_id)
                    || plugin.install_policy == PluginInstallPolicy::NotAvailable
                    || plugin.availability == PluginAvailability::DisabledByAdmin
                    || is_disabled
                    || (!is_configured_plugin && !is_fallback_plugin && !matches_installed_app)
                {
                    continue;
                }

                discoverable_plugins.push(ToolSuggestDiscoverablePlugin {
                    id: plugin.config_id,
                    remote_plugin_id: Some(plugin.remote_plugin_id),
                    name: plugin.name,
                    description: plugin.description,
                    has_skills: plugin.has_skills,
                    mcp_server_names: Vec::new(),
                    app_connector_ids: plugin.app_ids,
                });
            }
        }
        discoverable_plugins.sort_by(|left, right| {
            left.name
                .cmp(&right.name)
                .then_with(|| left.id.cmp(&right.id))
        });
        Ok(discoverable_plugins)
    }
}

#[cfg(test)]
#[path = "discoverable_tests.rs"]
mod tests;
