use clap::*;
use log::debug;
use std::path::PathBuf;
use std::str::FromStr;

/// Helper function to convert serde_json::Value to serde_yaml::Value.
///
/// Recursively converts JSON values to equivalent YAML values, handling null, bool, number,
/// string, array, and object types.
///
/// # Arguments
///
/// * `value` - Reference to the JSON value to convert.
///
/// # Returns
///
/// The equivalent YAML value.
fn convert_json_to_yaml_value(value: &serde_json::Value) -> serde_yaml::Value {
    match value {
        serde_json::Value::Null => serde_yaml::Value::Null,
        serde_json::Value::Bool(b) => serde_yaml::Value::Bool(*b),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                serde_yaml::Value::Number(serde_yaml::Number::from(i))
            } else if let Some(f) = n.as_f64() {
                serde_yaml::Value::Number(serde_yaml::Number::from(f))
            } else {
                serde_yaml::Value::String(n.to_string())
            }
        }
        serde_json::Value::String(s) => serde_yaml::Value::String(s.clone()),
        serde_json::Value::Array(arr) => {
            let mut yaml_array = Vec::new();
            for item in arr {
                yaml_array.push(convert_json_to_yaml_value(item));
            }
            serde_yaml::Value::Sequence(yaml_array)
        }
        serde_json::Value::Object(obj) => {
            let mut yaml_mapping = serde_yaml::Mapping::new();
            for (k, v) in obj {
                yaml_mapping.insert(
                    serde_yaml::Value::String(k.clone()),
                    convert_json_to_yaml_value(v),
                );
            }
            serde_yaml::Value::Mapping(yaml_mapping)
        }
    }
}

use crate::config;
use crate::modes::common::OutputFormat;
use comfy_table::{Attribute, Cell, Table};
use serde_json;
use serde_yaml;

use crate::common::status::{CompressionInfo, MetaPluginInfo};
use crate::meta_plugin::{MetaPluginType, get_meta_plugin};

fn build_meta_plugin_table(
    meta_plugin_info: &std::collections::HashMap<String, MetaPluginInfo>,
    table_config: &crate::config::TableConfig,
) -> Table {
    // Builds a formatted table displaying meta plugin information.
    //
    // Sorts plugins by name and displays options as YAML and outputs as a list.
    //
    // # Arguments
    //
    // * `meta_plugin_info` - HashMap of meta plugin information.
    //
    // # Returns
    //
    // A formatted `comfy_table::Table`.
    let mut meta_plugin_table = crate::modes::common::create_table_with_config(table_config);

    meta_plugin_table.set_header(vec![
        Cell::new("Plugin Name").add_attribute(Attribute::Bold),
        Cell::new("Options").add_attribute(Attribute::Bold),
        Cell::new("Outputs").add_attribute(Attribute::Bold),
    ]);

    // Sort meta plugin info by plugin name
    let mut sorted_meta_plugin_info: Vec<&MetaPluginInfo> = meta_plugin_info.values().collect();
    sorted_meta_plugin_info.sort_by(|a, b| a.meta_name.cmp(&b.meta_name));

    for info in sorted_meta_plugin_info {
        // Get default options for the meta plugin
        let meta_plugin_type = match MetaPluginType::from_str(&info.meta_name) {
            Ok(plugin_type) => plugin_type,
            Err(_) => continue,
        };

        // Create a default plugin to get its default options
        let Ok(default_plugin) = get_meta_plugin(meta_plugin_type.clone(), None, None) else {
            continue;
        };

        // Get and sort options
        let mut options: Vec<_> = default_plugin.options().iter().collect();
        options.sort_by(|a, b| a.0.cmp(b.0));

        // Format options as YAML string, each on a new line
        let options_str = if options.is_empty() {
            "{}".to_string()
        } else {
            let options_map: std::collections::BTreeMap<_, _> = options.into_iter().collect();
            serde_yaml::to_string(&options_map)
                .unwrap_or_else(|_| "Unable to serialize options".to_string())
                .trim()
                .to_string()
        };

        // Get and sort output keys
        let mut output_keys: Vec<String> = info.outputs.keys().map(|k| k.to_string()).collect();
        output_keys.sort();
        let outputs_display = if output_keys.is_empty() {
            "{}".to_string()
        } else {
            output_keys.join("\n")
        };

        meta_plugin_table.add_row(vec![info.meta_name.clone(), options_str, outputs_display]);
    }

    meta_plugin_table
}

fn build_compression_table(
    compression_info: &Vec<CompressionInfo>,
    table_config: &crate::config::TableConfig,
) -> Table {
    // Builds a formatted table displaying compression plugin information.
    //
    // # Arguments
    //
    // * `compression_info` - Vector of compression info.
    //
    // # Returns
    //
    // A formatted `comfy_table::Table`.
    let mut compression_table = crate::modes::common::create_table_with_config(table_config);

    compression_table.set_header(vec![
        Cell::new("Type").add_attribute(Attribute::Bold),
        Cell::new("Found").add_attribute(Attribute::Bold),
        Cell::new("Enabled").add_attribute(Attribute::Bold),
        Cell::new("Binary").add_attribute(Attribute::Bold),
        Cell::new("Compress").add_attribute(Attribute::Bold),
        Cell::new("Decompress").add_attribute(Attribute::Bold),
    ]);

    for info in compression_info {
        compression_table.add_row(vec![
            info.compression_type.clone(),
            match info.found {
                true => "Yes".to_string(),
                false => "No".to_string(),
            },
            match info.default {
                true => "Yes".to_string(),
                false => "No".to_string(),
            },
            info.binary.clone(),
            info.compress.clone(),
            info.decompress.clone(),
        ]);
    }

    compression_table
}

fn build_filter_plugin_table(
    filter_plugins: &[crate::common::status::FilterPluginInfo],
    table_config: &crate::config::TableConfig,
) -> Table {
    // Builds a formatted table displaying filter plugin information.
    //
    // Sorts plugins by name and formats options as YAML sequence.
    //
    // # Arguments
    //
    // * `filter_plugins` - Vector of filter plugin info.
    //
    // # Returns
    //
    // A formatted `comfy_table::Table`.
    let mut filter_plugin_table = crate::modes::common::create_table_with_config(table_config);

    filter_plugin_table.set_header(vec![
        Cell::new("Plugin Name").add_attribute(Attribute::Bold),
        Cell::new("Options").add_attribute(Attribute::Bold),
        Cell::new("Description").add_attribute(Attribute::Bold),
    ]);

    // Sort plugins by name
    let mut sorted_plugins: Vec<_> = filter_plugins.iter().collect();
    sorted_plugins.sort_by(|a, b| a.name.cmp(&b.name));

    for plugin_info in sorted_plugins {
        // Format options as YAML string
        let options_str = if plugin_info.options.is_empty() {
            "{}".to_string()
        } else {
            // Convert options to a proper structure for display
            let mut options_list = Vec::new();
            for opt in &plugin_info.options {
                let mut opt_map = serde_yaml::Mapping::new();
                opt_map.insert(
                    serde_yaml::Value::String("name".to_string()),
                    serde_yaml::Value::String(opt.name.clone()),
                );
                if let Some(default) = &opt.default {
                    // Convert serde_json::Value to serde_yaml::Value
                    let yaml_value = match default {
                        serde_json::Value::Null => serde_yaml::Value::Null,
                        serde_json::Value::Bool(b) => serde_yaml::Value::Bool(*b),
                        serde_json::Value::Number(n) => {
                            if let Some(i) = n.as_i64() {
                                serde_yaml::Value::Number(serde_yaml::Number::from(i))
                            } else if let Some(f) = n.as_f64() {
                                serde_yaml::Value::Number(serde_yaml::Number::from(f))
                            } else {
                                serde_yaml::Value::String(default.to_string())
                            }
                        }
                        serde_json::Value::String(s) => serde_yaml::Value::String(s.clone()),
                        serde_json::Value::Array(arr) => {
                            // Convert each element in the array
                            let mut yaml_array = Vec::new();
                            for item in arr {
                                yaml_array.push(convert_json_to_yaml_value(item));
                            }
                            serde_yaml::Value::Sequence(yaml_array)
                        }
                        serde_json::Value::Object(obj) => {
                            // Convert each key-value pair in the object
                            let mut yaml_mapping = serde_yaml::Mapping::new();
                            for (k, v) in obj {
                                yaml_mapping.insert(
                                    serde_yaml::Value::String(k.clone()),
                                    convert_json_to_yaml_value(v),
                                );
                            }
                            serde_yaml::Value::Mapping(yaml_mapping)
                        }
                    };
                    opt_map.insert(serde_yaml::Value::String("default".to_string()), yaml_value);
                } else {
                    opt_map.insert(
                        serde_yaml::Value::String("default".to_string()),
                        serde_yaml::Value::Null,
                    );
                }
                opt_map.insert(
                    serde_yaml::Value::String("required".to_string()),
                    serde_yaml::Value::Bool(opt.required),
                );
                options_list.push(serde_yaml::Value::Mapping(opt_map));
            }
            serde_yaml::to_string(&serde_yaml::Value::Sequence(options_list))
                .unwrap_or_else(|_| "Unable to serialize options".to_string())
                .trim()
                .to_string()
        };

        filter_plugin_table.add_row(vec![
            plugin_info.name.clone(),
            options_str,
            plugin_info.description.clone(),
        ]);
    }

    // If no filter plugins are available, add a row indicating that
    if filter_plugins.is_empty() {
        filter_plugin_table.add_row(vec!["No filter plugins available", "{}", ""]);
    }

    filter_plugin_table
}

pub fn mode_status_plugins(
    cmd: &mut Command,
    settings: &config::Settings,
    data_path: PathBuf,
    db_path: PathBuf,
) -> Result<(), anyhow::Error> {
    // Displays status information for available plugins in the specified output format.
    //
    // Generates status using StatusService and renders as table, JSON, or YAML.
    //
    // # Arguments
    //
    // * `cmd` - Mutable Clap command.
    // * `settings` - Application settings.
    // * `data_path` - Data directory path.
    // * `db_path` - Database path.
    //
    // # Returns
    //
    // `Ok(())` on success, or anyhow::Error.
    debug!("STATUS_PLUGINS: Starting mode_status_plugins function");

    let status_service = crate::services::status_service::StatusService::new();
    let output_format = crate::modes::common::settings_output_format(settings);
    debug!("STATUS_PLUGINS: About to generate status info");
    let status_info = status_service.generate_status(cmd, settings, data_path, db_path)?;
    debug!("STATUS_PLUGINS: Status info generated successfully");

    match output_format {
        OutputFormat::Table => {
            println!("META PLUGINS:");
            let meta_table =
                build_meta_plugin_table(&status_info.meta_plugins, &settings.table_config);
            println!(
                "{}",
                crate::modes::common::trim_lines_end(&meta_table.trim_fmt())
            );
            println!();

            println!("COMPRESSION PLUGINS:");
            let compression_table =
                build_compression_table(&status_info.compression, &settings.table_config);
            println!(
                "{}",
                crate::modes::common::trim_lines_end(&compression_table.trim_fmt())
            );
            println!();

            println!("FILTER PLUGINS:");
            let filter_table =
                build_filter_plugin_table(&status_info.filter_plugins, &settings.table_config);
            println!(
                "{}",
                crate::modes::common::trim_lines_end(&filter_table.trim_fmt())
            );
            println!();
            Ok(())
        }
        OutputFormat::Json => {
            // Create a subset for plugins only using status_info
            let plugins_info = serde_json::json!({
                "meta_plugins_available": status_info.meta_plugins,
                "meta_plugins_configured": status_info.configured_meta_plugins,
                "filter_plugins": status_info.filter_plugins
            });
            println!("{}", serde_json::to_string_pretty(&plugins_info)?);
            Ok(())
        }
        OutputFormat::Yaml => {
            // Create a proper structure for plugins info using status_info
            use serde_yaml::Mapping;
            let mut plugins_mapping = Mapping::new();

            // Add available plugins
            plugins_mapping.insert(
                serde_yaml::Value::String("meta_plugins_available".to_string()),
                serde_yaml::to_value(&status_info.meta_plugins)?,
            );

            // Add configured plugins if they exist
            if let Some(configured_plugins) = &status_info.configured_meta_plugins {
                plugins_mapping.insert(
                    serde_yaml::Value::String("meta_plugins_configured".to_string()),
                    serde_yaml::to_value(configured_plugins)?,
                );
            }

            // Add filter plugins
            plugins_mapping.insert(
                serde_yaml::Value::String("filter_plugins".to_string()),
                serde_yaml::to_value(&status_info.filter_plugins)?,
            );

            println!("{}", serde_yaml::to_string(&plugins_mapping)?);
            Ok(())
        }
    }
}
