//! CLI code generation for Universal Tool Framework
//!
//! This module generates `create_cli_command()` and `execute_cli()` methods
//! that integrate tools with clap-based CLI applications.

use crate::codegen::shared::to_kebab_case;
use crate::codegen::shared::type_to_string;
use crate::codegen::validation;
use crate::model::ParamSource;
use crate::model::RouterDef;
use crate::model::ToolDef;
use proc_macro2::TokenStream;
use quote::quote;

/// Generates all CLI-related methods for a router
pub fn generate_cli_methods(router: &RouterDef) -> TokenStream {
    let struct_type = &router.struct_type;
    let create_command_method = generate_create_cli_command(router);
    let execute_cli_method = generate_execute_cli(router);
    let completions_method = generate_completions_method();

    quote! {
        impl #struct_type {
            #create_command_method

            #execute_cli_method

            #completions_method
        }
    }
}

/// Generates the create_cli_command() method
fn generate_create_cli_command(router: &RouterDef) -> TokenStream {
    // Use CLI configuration if provided, otherwise use struct name
    let cli_name = router
        .metadata
        .cli_config
        .as_ref()
        .and_then(|c| c.name.as_ref())
        .cloned()
        .unwrap_or_else(|| {
            router
                .struct_type
                .segments
                .last()
                .map(|s| s.ident.to_string().to_lowercase())
                .unwrap_or_else(|| "tools".to_string())
        });

    let about = router
        .metadata
        .cli_config
        .as_ref()
        .and_then(|c| c.description.as_ref())
        .map(|d| d.as_str())
        .unwrap_or("Tool suite generated by UTF");

    // Generate subcommands
    // TODO(2): Support command_path for nested subcommands
    let subcommands: Vec<_> = router.tools.iter().map(generate_tool_subcommand).collect();

    // Debug: Print number of subcommands
    if std::env::var("UTF_DEBUG").is_ok() {
        eprintln!("Generating {} subcommands", subcommands.len());
    }

    // Add global output format argument if configured
    let global_args = if let Some(cli_config) = &router.metadata.cli_config {
        if !cli_config.global_output_formats.is_empty() {
            let formats = cli_config.global_output_formats.clone();
            quote! {
                .arg(
                    Arg::new("output-format")
                        .long("output")
                        .short('o')
                        .help("Output format")
                        .value_parser([#(#formats),*])
                        .default_value("text")
                        .global(true)
                )
            }
        } else {
            quote! {}
        }
    } else {
        quote! {}
    };

    // Add standard global args if configured
    let standard_args = if let Some(cli_config) = &router.metadata.cli_config {
        if cli_config.standard_global_args {
            quote! {
                .arg(
                    Arg::new("dry-run")
                        .long("dry-run")
                        .help("Run without making changes")
                        .action(ArgAction::SetTrue)
                        .global(true)
                )
                .arg(
                    Arg::new("yes")
                        .long("yes")
                        .short('y')
                        .help("Skip confirmation prompts")
                        .action(ArgAction::SetTrue)
                        .global(true)
                )
                .arg(
                    Arg::new("quiet")
                        .long("quiet")
                        .short('q')
                        .help("Suppress non-error output")
                        .action(ArgAction::SetTrue)
                        .global(true)
                )
                .arg(
                    Arg::new("verbose")
                        .long("verbose")
                        .short('v')
                        .help("Increase verbosity")
                        .action(ArgAction::Count)
                        .global(true)
                )
            }
        } else {
            quote! {}
        }
    } else {
        quote! {}
    };

    // Check if we actually have global/standard args
    let has_global_args = router
        .metadata
        .cli_config
        .as_ref()
        .map(|c| !c.global_output_formats.is_empty())
        .unwrap_or(false);

    let has_standard_args = router
        .metadata
        .cli_config
        .as_ref()
        .map(|c| c.standard_global_args)
        .unwrap_or(false);

    // Build command initialization based on what we have
    let cmd_init = match (has_global_args, has_standard_args) {
        (true, true) => quote! {
            let mut cmd = Command::new(#cli_name)
                .about(#about)
                .version(env!("CARGO_PKG_VERSION"))
                #global_args
                #standard_args;
        },
        (true, false) => quote! {
            let mut cmd = Command::new(#cli_name)
                .about(#about)
                .version(env!("CARGO_PKG_VERSION"))
                #global_args;
        },
        (false, true) => quote! {
            let mut cmd = Command::new(#cli_name)
                .about(#about)
                .version(env!("CARGO_PKG_VERSION"))
                #standard_args;
        },
        (false, false) => quote! {
            let mut cmd = Command::new(#cli_name)
                .about(#about)
                .version(env!("CARGO_PKG_VERSION"));
        },
    };

    quote! {
        /// Creates a clap::Command configured with all tools as subcommands
        pub fn create_cli_command(&self) -> ::universal_tool_core::cli::clap::Command {
            use ::universal_tool_core::cli::clap::{Command, Arg, ArgAction};

            #cmd_init

            // Test re-enabling this expansion
            #(#subcommands)*

            cmd
        }
    }
}

/// Generates a subcommand for a single tool
fn generate_tool_subcommand(tool: &ToolDef) -> TokenStream {
    // Use CLI name if configured, otherwise use tool_name or method_name
    let cli_name_str = tool
        .metadata
        .cli_config
        .as_ref()
        .and_then(|c| c.name.as_ref())
        .map(|n| to_kebab_case(n))
        .unwrap_or_else(|| {
            if !tool.tool_name.is_empty() {
                to_kebab_case(&tool.tool_name)
            } else {
                to_kebab_case(&tool.method_name.to_string())
            }
        });

    let about = &tool.metadata.description;

    let args: Vec<_> = tool
        .params
        .iter()
        .filter(|p| matches!(p.source, ParamSource::Body))
        .map(validation::generate_cli_arg_config)
        .collect();

    // Generate alias calls if configured
    let alias_calls = if let Some(cli_config) = &tool.metadata.cli_config {
        cli_config
            .aliases
            .iter()
            .map(|alias| {
                quote! { .alias(#alias) }
            })
            .collect::<Vec<_>>()
    } else {
        vec![]
    };

    // Build the subcommand
    if std::env::var("UTF_DEBUG").is_ok() {
        eprintln!(
            "Building subcommand for tool: {} with {} args",
            cli_name_str,
            args.len()
        );
    }

    let mut subcommand = quote! {
        Command::new(#cli_name_str)
            .about(#about)
    };

    // Add aliases
    for alias_call in &alias_calls {
        subcommand = quote! { #subcommand #alias_call };
    }

    // Add hidden flag if configured
    if let Some(cli_config) = &tool.metadata.cli_config
        && cli_config.hidden
    {
        subcommand = quote! { #subcommand .hide(true) };
    }

    // Add arguments
    for (i, arg) in args.iter().enumerate() {
        if std::env::var("UTF_DEBUG").is_ok() {
            eprintln!("  Adding arg {i}: {arg}");
        }
        subcommand = quote! { #subcommand .arg(#arg) };
    }

    quote! {
        cmd = cmd.subcommand(#subcommand);
    }
}

/// Generates the execute_cli() method
fn generate_execute_cli(router: &RouterDef) -> TokenStream {
    let match_arms: Vec<_> = router
        .tools
        .iter()
        .map(|tool| generate_execute_match_arm(tool, router))
        .collect();

    quote! {
        /// Executes a tool based on parsed CLI arguments
        pub async fn execute_cli(&self, matches: ::universal_tool_core::cli::clap::ArgMatches) -> Result<(), ::universal_tool_core::prelude::ToolError> {
            match matches.subcommand() {
                #(#match_arms,)*
                Some((cmd, _)) => {
                    eprintln!("Error: Unknown command '{}'", cmd);
                    eprintln!("Try '--help' for more information.");
                    Err(::universal_tool_core::prelude::ToolError::new(
                        ::universal_tool_core::prelude::ErrorCode::NotFound,
                        format!("Unknown command: {}", cmd)
                    ))
                },
                None => {
                    eprintln!("No command specified");
                    eprintln!("Try '--help' for more information.");
                    Err(::universal_tool_core::prelude::ToolError::new(
                        ::universal_tool_core::prelude::ErrorCode::InvalidArgument,
                        "No command specified"
                    ))
                }
            }
        }
    }
}

/// Generates a match arm for executing a tool
fn generate_execute_match_arm(tool: &ToolDef, router: &RouterDef) -> TokenStream {
    // Use CLI name if configured, otherwise use tool_name or method_name
    let cli_name_str = tool
        .metadata
        .cli_config
        .as_ref()
        .and_then(|c| c.name.as_ref())
        .map(|n| to_kebab_case(n))
        .unwrap_or_else(|| {
            if !tool.tool_name.is_empty() {
                to_kebab_case(&tool.tool_name)
            } else {
                to_kebab_case(&tool.method_name.to_string())
            }
        });

    // Use the validation module's parameter extraction which handles types correctly
    let param_extractions = validation::generate_params_extraction(tool, "cli");

    // With our new extraction, all values are already owned
    // For CLI, we include all parameters regardless of source (query/path/body are REST concepts)
    let param_names: Vec<_> = tool.params.iter().map(|p| &p.name).collect();

    // Check if tool has output formats configured
    let has_output_formats = tool
        .metadata
        .cli_config
        .as_ref()
        .map(|c| !c.output_formats.is_empty())
        .unwrap_or(false);

    // Check if global output formats are configured
    let _output_handling = if has_output_formats
        && router
            .metadata
            .cli_config
            .as_ref()
            .map(|c| !c.global_output_formats.is_empty())
            .unwrap_or(false)
    {
        quote! {
            // Get the global output format flag (it's available in parent matches)
            let output_format = matches.get_one::<String>("output-format")
                .map(|s| ::universal_tool_core::cli::OutputFormat::from_str(s).unwrap_or(::universal_tool_core::cli::OutputFormat::Text))
                .unwrap_or(::universal_tool_core::cli::OutputFormat::Text);

            // Use CliFormatter if available, otherwise fall back to JSON
            use ::universal_tool_core::cli::CliFormatter;
            let formatted = result.format_output(output_format)?;
            println!("{}", formatted);
        }
    } else {
        // Check return type to determine output handling
        let ty_str = type_to_string(&tool.return_type);
        if ty_str.contains("String") {
            quote! {
                // String output - print directly
                println!("{}", result);
            }
        } else {
            quote! {
                // No output formatting configured, use JSON
                println!("{}", ::serde_json::to_string_pretty(&result)?);
            }
        }
    };

    // TODO(2): Support interactive mode for parameter collection
    // This would require restructuring parameter extraction to merge CLI args
    // with interactively collected values

    // Generate confirmation check if configured
    let _confirm_check = if let Some(cli_config) = &tool.metadata.cli_config {
        if let Some(confirm_msg) = &cli_config.confirm {
            // Check if --yes global flag is set to skip confirmation
            quote! {
                // Check if --yes flag is set
                if !matches.get_flag("yes") {
                    // Count the parameters for the prompt
                    let count = sub_matches.ids()
                        .filter_map(|id| sub_matches.get_many::<String>(id.as_str()))
                        .map(|values| values.len())
                        .sum::<usize>();

                    let confirm_msg = #confirm_msg.replace("{count}", &count.to_string());

                    if !::universal_tool_core::cli::interactive::confirm(&confirm_msg, false)? {
                        println!("Operation cancelled.");
                        return Ok(());
                    }
                }
            }
        } else {
            quote! {}
        }
    } else {
        quote! {}
    };

    // Build the arguments vector for the method call
    let param_args_vec: Vec<TokenStream> =
        param_names.iter().map(|name| quote! { #name }).collect();

    // Use the shared function to generate the method call with proper async/sync handling
    let method_call = crate::codegen::shared::generate_normalized_method_call(
        tool,
        quote! { self },
        param_args_vec,
    );

    // Build the match arm based on whether we have a confirm check
    let _has_confirm = tool
        .metadata
        .cli_config
        .as_ref()
        .and_then(|c| c.confirm.as_ref())
        .is_some();

    // Generate the match arm
    // Note: We need to ensure proper statement separation when expanding param_extractions
    quote! {
        Some((#cli_name_str, sub_matches)) => {
            #( #param_extractions )*

            let result = #method_call?;

            // Simple output handling for now
            match ::serde_json::to_string_pretty(&result) {
                Ok(json) => {
                    println!("{}", json);
                    Ok(())
                },
                Err(e) => Err(::universal_tool_core::prelude::ToolError::new(
                    ::universal_tool_core::prelude::ErrorCode::SerializationError,
                    format!("Failed to serialize result: {}", e)
                ))
            }
        }
    }
}

// Helper functions

// Type checking functions are imported from shared module

/// Generates the generate_completions() method
fn generate_completions_method() -> TokenStream {
    quote! {
        /// Generate shell completions for the CLI
        pub fn generate_completions(&self, shell: ::universal_tool_core::cli::clap_complete::Shell) -> String {
            use std::io::Cursor;

            let mut cmd = self.create_cli_command();
            let name = cmd.get_name().to_string();
            let mut buffer = Cursor::new(Vec::new());

            ::universal_tool_core::cli::clap_complete::generate(shell, &mut cmd, name, &mut buffer);

            String::from_utf8(buffer.into_inner())
                .expect("Shell completion generation should produce valid UTF-8")
        }
    }
}

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

    #[test]
    fn test_to_kebab_case() {
        assert_eq!(to_kebab_case("camelCase"), "camel-case");
        assert_eq!(to_kebab_case("snake_case"), "snake-case");
        assert_eq!(to_kebab_case("PascalCase"), "pascal-case");
        assert_eq!(to_kebab_case("lowercase"), "lowercase");
        assert_eq!(to_kebab_case("UPPERCASE"), "uppercase");
    }
}
