#![deny(warnings)]

use std::env;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::process::Command;

// NOTE: We no longer use hardcoded signatures. Signatures MUST be extracted from nginx source
// to ensure binary compatibility. If signature extraction fails, the build will fail.

fn main() {
    println!("cargo:rerun-if-changed=build.rs");

    // Auto-download nginx source if NGINX_SOURCE_DIR is not set
    // This needs to happen early so nginx-sys can use it
    if env::var("NGINX_SOURCE_DIR").is_err() {
        if let Ok(source_dir) = auto_download_nginx_source() {
            // Set environment variable for nginx-sys (though it may have already run)
            // This is best-effort - nginx-sys may need NGINX_SOURCE_DIR set before cargo build
            println!("cargo:rustc-env=NGINX_SOURCE_DIR={}", source_dir.display());
            // Also set it for our own use
            env::set_var("NGINX_SOURCE_DIR", source_dir.to_str().unwrap());
        }
    }

    // Extract nginx module signature - try binary first, then source
    // Priority: 1. System nginx binary (if NGINX_BINARY_PATH is set)
    //           2. Nginx source configuration (if NGINX_SOURCE_DIR is set)
    let signature = extract_nginx_module_signature().unwrap_or_else(|e| {
        eprintln!("cargo:error=Failed to extract nginx module signature");
        eprintln!("cargo:error=Error: {}", e);
        eprintln!("cargo:error=");
        eprintln!("cargo:error=Module signature MUST be extracted to ensure binary compatibility.");
        eprintln!("cargo:error=Please ensure one of the following:");
        eprintln!("cargo:error=");
        eprintln!("cargo:error=Option 1: Extract from system nginx binary (recommended for system nginx)");
        eprintln!("cargo:error=  Set NGINX_BINARY_PATH to the path of system nginx binary");
        eprintln!("cargo:error=  Example: export NGINX_BINARY_PATH=/usr/sbin/nginx");
        eprintln!("cargo:error=");
        eprintln!("cargo:error=Option 2: Extract from nginx source configuration");
        eprintln!("cargo:error=  1. NGINX_SOURCE_DIR is set to a configured nginx source directory");
        eprintln!("cargo:error=  2. The nginx source has been configured (./configure has been run)");
        eprintln!("cargo:error=  3. objs/ngx_auto_config.h exists in the source directory");
        eprintln!("cargo:error=");
        eprintln!("cargo:error=Note: Requires nginx 1.10.0 or later (released April 2016).");
        eprintln!("cargo:error=");
        eprintln!("cargo:error=Hardcoded signatures are not allowed - they will cause binary incompatibility.");
        std::process::exit(1);
    });

    // Print extracted signature for debugging
    eprintln!(
        "cargo:warning=Extracted nginx module signature: {}",
        signature
    );
    eprintln!("cargo:warning=Signature format: {{NGX_PTR_SIZE}},{{NGX_SIG_ATOMIC_T_SIZE}},{{NGX_TIME_T_SIZE}},{{feature_flags}}");

    // Generate a Rust file with the signature
    generate_signature_file(&signature).unwrap_or_else(|e| {
        eprintln!("cargo:error=Failed to generate signature file: {}", e);
        std::process::exit(1);
    });

    // Configure linker flags for dynamic nginx modules
    configure_linker_flags();
}

/// Generate the module signature Rust file.
fn generate_signature_file(signature: &str) -> io::Result<()> {
    let out_dir =
        env::var("OUT_DIR").map_err(|e| io::Error::other(format!("OUT_DIR not set: {}", e)))?;

    let signature_file = PathBuf::from(&out_dir).join("module_signature.rs");

    let signature_code = format!(
        r#"// Auto-generated module signature
// This file is generated by build.rs from nginx source configuration
// DO NOT EDIT MANUALLY - changes will be overwritten
pub const MODULE_SIGNATURE: &[u8] = b"{}\0";
"#,
        signature
    );

    fs::write(&signature_file, signature_code)?;
    println!("cargo:rerun-if-changed={}", signature_file.display());

    Ok(())
}

/// Configure linker flags for dynamic nginx modules.
///
/// Dynamic nginx modules need to allow undefined symbols because nginx symbols
/// will be resolved at runtime when the module is loaded by nginx.
fn configure_linker_flags() {
    // macOS requires explicit flags to allow undefined symbols
    #[cfg(target_os = "macos")]
    {
        println!("cargo:rustc-link-arg=-undefined");
        println!("cargo:rustc-link-arg=dynamic_lookup");
    }

    // Linux shared libraries (.so) allow undefined symbols by default
    // The symbols will be resolved when nginx loads the module at runtime
}

/// Extract nginx module signature from binary or source files.
///
/// The signature format is: "{NGX_PTR_SIZE},{NGX_SIG_ATOMIC_T_SIZE},{NGX_TIME_T_SIZE},{feature_flags}"
///
/// Priority:
/// 1. Extract from system nginx binary (if NGINX_BINARY_PATH is set)
///    - If NGINX_BINARY_PATH is set, also extract from source config and compare
///    - If signatures don't match, emit a strong warning but use system binary signature
/// 2. Extract from nginx source configuration (objs/ngx_auto_config.h)
///
/// Requires nginx 1.10.0 or later (released April 2016).
/// Returns an error if signature cannot be extracted.
fn extract_nginx_module_signature() -> io::Result<String> {
    // Priority 1: Try to extract from system nginx binary
    if let Ok(binary_path) = env::var("NGINX_BINARY_PATH") {
        let path = PathBuf::from(binary_path);
        if path.exists() {
            eprintln!(
                "cargo:warning=Attempting to extract signature from nginx binary: {}",
                path.display()
            );
            if let Ok(binary_signature) = extract_signature_from_binary(&path) {
                eprintln!(
                    "cargo:warning=Successfully extracted signature from nginx binary: {}",
                    binary_signature
                );

                // Also try to extract from source config for comparison
                if let Ok(nginx_source_dir) = env::var("NGINX_SOURCE_DIR") {
                    let source_dir = PathBuf::from(nginx_source_dir);
                    let auto_config_path = source_dir.join("objs/ngx_auto_config.h");

                    if auto_config_path.exists() {
                        match fs::read_to_string(&auto_config_path) {
                            Ok(mut config_content) => {
                                // Also read ngx_auto_headers.h if it exists
                                let auto_headers_path = source_dir.join("objs/ngx_auto_headers.h");
                                if auto_headers_path.exists() {
                                    if let Ok(headers_content) =
                                        fs::read_to_string(&auto_headers_path)
                                    {
                                        config_content.push('\n');
                                        config_content.push_str(&headers_content);
                                    }
                                }

                                // Check autoconf.err for SO_ACCEPTFILTER availability
                                let autoconf_err_path = source_dir.join("objs/autoconf.err");
                                let has_so_acceptfilter = if autoconf_err_path.exists() {
                                    if let Ok(autoconf_content) =
                                        fs::read_to_string(&autoconf_err_path)
                                    {
                                        !autoconf_content.contains("'SO_ACCEPTFILTER' undeclared")
                                            && !autoconf_content.contains("SO_ACCEPTFILTER.*error")
                                    } else {
                                        false
                                    }
                                } else {
                                    false
                                };

                                if has_so_acceptfilter {
                                    config_content
                                        .push_str("\n#define SO_ACCEPTFILTER_AVAILABLE 1");
                                } else {
                                    config_content.push_str("\n#undef SO_ACCEPTFILTER_AVAILABLE");
                                }

                                // Try to read ngx_atomic.h for NGX_HAVE_ATOMIC_OPS
                                let atomic_h_path = source_dir.join("src/os/unix/ngx_atomic.h");
                                if atomic_h_path.exists() {
                                    if let Ok(atomic_content) = fs::read_to_string(&atomic_h_path) {
                                        for line in atomic_content.lines() {
                                            if line
                                                .trim()
                                                .starts_with("#define NGX_HAVE_ATOMIC_OPS")
                                            {
                                                config_content.push('\n');
                                                config_content.push_str(line.trim());
                                                break;
                                            }
                                        }
                                    }
                                }

                                // Build signature from source config
                                if let Some(source_signature) =
                                    build_signature_from_config_h(&config_content)
                                {
                                    eprintln!(
                                        "cargo:warning=Extracted signature from source config: {}",
                                        source_signature
                                    );

                                    // Compare signatures
                                    if binary_signature != source_signature {
                                        eprintln!("cargo:warning=");
                                        eprintln!("cargo:warning=╔══════════════════════════════════════════════════════════════════════════════╗");
                                        eprintln!("cargo:warning=║                    ⚠️  SIGNATURE MISMATCH WARNING ⚠️                        ║");
                                        eprintln!("cargo:warning=╠══════════════════════════════════════════════════════════════════════════════╣");
                                        eprintln!("cargo:warning=║                                                                              ║");
                                        eprintln!("cargo:warning=║  The signature extracted from nginx source configuration does NOT match   ║");
                                        eprintln!("cargo:warning=║  the signature from the system nginx binary!                                 ║");
                                        eprintln!("cargo:warning=║                                                                              ║");
                                        let binary_sig_formatted =
                                            format!("{:<50}", binary_signature);
                                        let source_sig_formatted =
                                            format!("{:<50}", source_signature);
                                        eprintln!(
                                            "cargo:warning=║  System nginx binary signature:  {}",
                                            binary_sig_formatted
                                        );
                                        eprintln!(
                                            "cargo:warning=║  Source config signature:        {}",
                                            source_sig_formatted
                                        );
                                        eprintln!("cargo:warning=║                                                                              ║");
                                        eprintln!("cargo:warning=║  This indicates that the nginx source configuration does not exactly match   ║");
                                        eprintln!("cargo:warning=║  the system nginx build configuration. The module will use the system      ║");
                                        eprintln!("cargo:warning=║  binary signature to ensure compatibility, but you should investigate why   ║");
                                        eprintln!("cargo:warning=║  the source configuration differs.                                           ║");
                                        eprintln!("cargo:warning=║                                                                              ║");
                                        eprintln!("cargo:warning=║  Possible causes:                                                           ║");
                                        eprintln!("cargo:warning=║    - Configure arguments don't match exactly                                 ║");
                                        eprintln!("cargo:warning=║    - Different nginx version                                                 ║");
                                        eprintln!("cargo:warning=║    - Platform-specific feature detection differences                         ║");
                                        eprintln!("cargo:warning=║                                                                              ║");
                                        eprintln!("cargo:warning=║  Using system binary signature to ensure module compatibility.               ║");
                                        eprintln!("cargo:warning=╚══════════════════════════════════════════════════════════════════════════════╝");
                                        eprintln!("cargo:warning=");
                                    } else {
                                        eprintln!("cargo:warning=✓ Signatures match! Source config signature matches system binary signature.");
                                    }
                                } else {
                                    eprintln!("cargo:warning=Could not build signature from source config for comparison");
                                }
                            }
                            Err(e) => {
                                eprintln!(
                                    "cargo:warning=Could not read source config for comparison: {}",
                                    e
                                );
                            }
                        }
                    } else {
                        eprintln!("cargo:warning=Source config file not found, skipping signature comparison");
                    }
                } else {
                    eprintln!(
                        "cargo:warning=NGINX_SOURCE_DIR not set, skipping signature comparison"
                    );
                }

                return Ok(binary_signature);
            } else {
                eprintln!(
                    "cargo:warning=Failed to extract signature from binary, falling back to source"
                );
            }
        } else {
            eprintln!(
                "cargo:warning=NGINX_BINARY_PATH set but file does not exist: {}",
                path.display()
            );
        }
    }

    // Priority 2: Extract from nginx source configuration
    // Try to get NGINX_SOURCE_DIR from environment
    let nginx_source_dir = if let Ok(dir) = env::var("NGINX_SOURCE_DIR") {
        PathBuf::from(dir)
    } else {
        // Auto-detect and download nginx source if not set
        auto_download_nginx_source().map_err(|e| {
            io::Error::other(format!(
                "NGINX_SOURCE_DIR not set and failed to auto-download nginx source: {}",
                e
            ))
        })?
    };

    // Verify source directory exists
    if !nginx_source_dir.exists() {
        return Err(io::Error::other(format!(
            "Nginx source directory does not exist: {}",
            nginx_source_dir.display()
        )));
    }

    let auto_config_path = nginx_source_dir.join("objs/ngx_auto_config.h");

    // Build signature from ngx_auto_config.h
    // This is the standard method for nginx 1.10.0+ where signature is defined
    // via macros in src/core/ngx_module.h and built from configure-time defines
    if auto_config_path.exists() {
        match fs::read_to_string(&auto_config_path) {
            Ok(mut config_content) => {
                // Also read ngx_auto_headers.h if it exists
                let auto_headers_path = nginx_source_dir.join("objs/ngx_auto_headers.h");
                if auto_headers_path.exists() {
                    if let Ok(headers_content) = fs::read_to_string(&auto_headers_path) {
                        config_content.push('\n');
                        config_content.push_str(&headers_content);
                    }
                }

                // Check autoconf.err for SO_ACCEPTFILTER availability
                // SO_ACCEPTFILTER is BSD-specific and typically not available on Linux
                let autoconf_err_path = nginx_source_dir.join("objs/autoconf.err");
                let has_so_acceptfilter = if autoconf_err_path.exists() {
                    if let Ok(autoconf_content) = fs::read_to_string(&autoconf_err_path) {
                        // If SO_ACCEPTFILTER check failed (undeclared/error), it's not available
                        // Check for error messages indicating SO_ACCEPTFILTER is not available
                        !autoconf_content.contains("'SO_ACCEPTFILTER' undeclared")
                            && !autoconf_content.contains("SO_ACCEPTFILTER.*error")
                    } else {
                        false
                    }
                } else {
                    false
                };

                // Store SO_ACCEPTFILTER availability in config_content for later use
                if has_so_acceptfilter {
                    config_content.push_str("\n#define SO_ACCEPTFILTER_AVAILABLE 1");
                } else {
                    config_content.push_str("\n#undef SO_ACCEPTFILTER_AVAILABLE");
                }

                // Try to read ngx_atomic.h for NGX_HAVE_ATOMIC_OPS
                // This is typically defined in src/os/unix/ngx_atomic.h based on platform capabilities
                let atomic_h_path = nginx_source_dir.join("src/os/unix/ngx_atomic.h");
                if atomic_h_path.exists() {
                    if let Ok(atomic_content) = fs::read_to_string(&atomic_h_path) {
                        // Look for NGX_HAVE_ATOMIC_OPS definition
                        for line in atomic_content.lines() {
                            if line.trim().starts_with("#define NGX_HAVE_ATOMIC_OPS") {
                                config_content.push('\n');
                                config_content.push_str(line.trim());
                                break;
                            }
                        }
                    }
                }

                // Debug: check for required defines
                let has_ptr_size = config_content.contains("NGX_PTR_SIZE");
                let has_sig_atomic = config_content.contains("NGX_SIG_ATOMIC_T_SIZE");
                let has_time_size = config_content.contains("NGX_TIME_T_SIZE");
                let has_module_sig = config_content.contains("NGX_MODULE_SIGNATURE_1");

                eprintln!("cargo:warning=ngx_auto_config.h content check:");
                eprintln!("cargo:warning=  NGX_PTR_SIZE: {}", has_ptr_size);
                eprintln!("cargo:warning=  NGX_SIG_ATOMIC_T_SIZE: {}", has_sig_atomic);
                eprintln!("cargo:warning=  NGX_TIME_T_SIZE: {}", has_time_size);
                eprintln!("cargo:warning=  NGX_MODULE_SIGNATURE_1: {}", has_module_sig);

                // Try to extract feature flags - they might be in a different format
                // Check for NGX_MODULE_SIGNATURE defines (might be numbered differently)
                let sig_defines: Vec<&str> = config_content
                    .lines()
                    .filter(|l| l.contains("NGX_MODULE_SIGNATURE"))
                    .take(5)
                    .collect();
                if !sig_defines.is_empty() {
                    eprintln!("cargo:warning=Found NGX_MODULE_SIGNATURE defines:");
                    for (i, line) in sig_defines.iter().enumerate() {
                        eprintln!("cargo:warning=  [{}] {}", i + 1, line.trim());
                    }
                }

                if let Some(signature) = build_signature_from_config_h(&config_content) {
                    eprintln!(
                        "cargo:warning=Built module signature from ngx_auto_config.h: {}",
                        signature
                    );
                    return Ok(signature);
                } else {
                    eprintln!(
                        "cargo:warning=Failed to build signature from ngx_auto_config.h content"
                    );
                    eprintln!("cargo:warning=One or more required defines are missing or feature flags are empty");

                    // Note: We cannot use default feature flags as they may not match the actual nginx build.
                    // The signature MUST be extracted from the actual nginx source configuration.
                }
            }
            Err(e) => {
                eprintln!("cargo:warning=Could not read ngx_auto_config.h: {}", e);
            }
        }
    } else {
        eprintln!(
            "cargo:warning=ngx_auto_config.h not found at: {}",
            auto_config_path.display()
        );
    }

    // No fallback - fail the build
    Err(io::Error::other(format!(
        "Failed to extract module signature from nginx source at: {}\n\
         objs/ngx_auto_config.h could not be used to extract signature.\n\
         Please ensure:\n\
         1. nginx source has been configured (./configure has been run)\n\
         2. nginx version is 1.10.0 or later (released April 2016)",
        nginx_source_dir.display()
    )))
}

/// Build signature from ngx_auto_config.h defines.
///
/// Constructs signature from:
/// - NGX_PTR_SIZE
/// - NGX_SIG_ATOMIC_T_SIZE
/// - NGX_TIME_T_SIZE
/// - NGX_MODULE_SIGNATURE_1..N (feature flags)
///
/// Returns None if required values cannot be extracted (no hardcoded fallbacks).
fn build_signature_from_config_h(content: &str) -> Option<String> {
    // Extract required values - return None if any are missing
    let ptr_size = extract_define_value(content, "NGX_PTR_SIZE")?;
    let sig_atomic_size = extract_define_value(content, "NGX_SIG_ATOMIC_T_SIZE")?;
    let time_size = extract_define_value(content, "NGX_TIME_T_SIZE")?;

    // Try to extract feature flags from NGX_MODULE_SIGNATURE_1..N first
    let mut feature_flags = extract_feature_flags(content);

    // If NGX_MODULE_SIGNATURE_* defines are not available, build from NGX_HAVE_* defines
    // This matches the logic in src/core/ngx_module.h
    if feature_flags.is_empty() {
        eprintln!(
            "cargo:warning=NGX_MODULE_SIGNATURE_* not found, building from NGX_HAVE_* defines..."
        );
        feature_flags = build_feature_flags_from_have_defines(content);
        eprintln!(
            "cargo:warning=Built feature flags from NGX_HAVE_*: {} (length: {})",
            feature_flags,
            feature_flags.len()
        );
    }

    // Feature flags must not be empty
    if feature_flags.is_empty() {
        eprintln!("cargo:warning=Feature flags are empty - cannot build signature");
        return None;
    }

    let signature = format!(
        "{},{},{},{}",
        ptr_size, sig_atomic_size, time_size, feature_flags
    );
    eprintln!(
        "cargo:warning=Built signature from ngx_auto_config.h: {}",
        signature
    );
    eprintln!(
        "cargo:warning=  PTR_SIZE={}, SIG_ATOMIC_T_SIZE={}, TIME_T_SIZE={}",
        ptr_size, sig_atomic_size, time_size
    );
    eprintln!(
        "cargo:warning=  Feature flags length: {} chars",
        feature_flags.len()
    );
    Some(signature)
}

/// Extract feature flags from NGX_MODULE_SIGNATURE_1..N defines.
fn extract_feature_flags(content: &str) -> String {
    let mut feature_flags = String::new();
    let mut sig_num = 1;

    loop {
        let sig_name = format!("NGX_MODULE_SIGNATURE_{}", sig_num);
        match extract_define_string(content, &sig_name) {
            Some(value) => {
                feature_flags.push_str(&value);
                sig_num += 1;
            }
            None => break,
        }
    }

    if feature_flags.is_empty() {
        // Return empty string - caller should handle this as an error
        String::new()
    } else {
        feature_flags
    }
}

/// Build feature flags from NGX_HAVE_* defines.
///
/// This matches the logic in src/core/ngx_module.h where NGX_MODULE_SIGNATURE_1..N
/// are defined based on NGX_HAVE_* macros.
fn build_feature_flags_from_have_defines(content: &str) -> String {
    let mut flags = String::new();

    // Build flags based on ngx_module.h logic
    // NGX_MODULE_SIGNATURE_1: NGX_HAVE_KQUEUE
    flags.push(if has_define(content, "NGX_HAVE_KQUEUE") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_2: NGX_HAVE_IOCP
    flags.push(if has_define(content, "NGX_HAVE_IOCP") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_3: NGX_HAVE_FILE_AIO || NGX_COMPAT
    flags.push(
        if has_define(content, "NGX_HAVE_FILE_AIO") || has_define(content, "NGX_COMPAT") {
            '1'
        } else {
            '0'
        },
    );

    // NGX_MODULE_SIGNATURE_4: NGX_HAVE_SENDFILE_NODISKIO || NGX_COMPAT
    flags.push(
        if has_define(content, "NGX_HAVE_SENDFILE_NODISKIO") || has_define(content, "NGX_COMPAT") {
            '1'
        } else {
            '0'
        },
    );

    // NGX_MODULE_SIGNATURE_5: NGX_HAVE_EVENTFD
    flags.push(if has_define(content, "NGX_HAVE_EVENTFD") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_6: NGX_HAVE_EPOLL
    flags.push(if has_define(content, "NGX_HAVE_EPOLL") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_7: NGX_HAVE_KEEPALIVE_TUNABLE
    flags.push(if has_define(content, "NGX_HAVE_KEEPALIVE_TUNABLE") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_8: NGX_HAVE_INET6
    flags.push(if has_define(content, "NGX_HAVE_INET6") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_9: Always "1"
    flags.push('1');

    // NGX_MODULE_SIGNATURE_10: Always "1"
    flags.push('1');

    // NGX_MODULE_SIGNATURE_11: NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER
    // Check both NGX_HAVE_DEFERRED_ACCEPT and SO_ACCEPTFILTER_AVAILABLE
    // SO_ACCEPTFILTER_AVAILABLE is set to 1 if available, undefined otherwise
    let sig11 = if has_define(content, "NGX_HAVE_DEFERRED_ACCEPT")
        && has_define(content, "SO_ACCEPTFILTER_AVAILABLE")
    {
        '1'
    } else {
        '0'
    };
    flags.push(sig11);

    // NGX_MODULE_SIGNATURE_12: Always "1"
    flags.push('1');

    // NGX_MODULE_SIGNATURE_13: NGX_HAVE_SETFIB
    flags.push(if has_define(content, "NGX_HAVE_SETFIB") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_14: NGX_HAVE_TCP_FASTOPEN
    flags.push(if has_define(content, "NGX_HAVE_TCP_FASTOPEN") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_15: NGX_HAVE_UNIX_DOMAIN
    flags.push(if has_define(content, "NGX_HAVE_UNIX_DOMAIN") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_16: NGX_HAVE_VARIADIC_MACROS
    // In nginx source (src/core/ngx_module.h), this is checked as:
    // #if (NGX_HAVE_C99_VARIADIC_MACROS || NGX_HAVE_GCC_VARIADIC_MACROS)
    //     NGX_MODULE_SIGNATURE_16 '1'
    // #else
    //     NGX_MODULE_SIGNATURE_16 '0'
    // #endif
    //
    // However, these defines might be in ngx_auto_headers.h, not ngx_auto_config.h
    // We need to check both files. Since we already merged ngx_auto_headers.h into content,
    // we should check the merged content.
    let has_c99 = has_define(content, "NGX_HAVE_C99_VARIADIC_MACROS");
    let has_gcc = has_define(content, "NGX_HAVE_GCC_VARIADIC_MACROS");
    let sig16 = if has_c99 || has_gcc { '1' } else { '0' };
    flags.push(sig16);
    eprintln!(
        "cargo:warning=NGX_MODULE_SIGNATURE_16 (variadic macros): {} (C99: {}, GCC: {})",
        sig16, has_c99, has_gcc
    );

    // Debug: show what defines are actually present
    if has_c99 || has_gcc {
        eprintln!("cargo:warning=  Found variadic macro support defines in content");
    } else {
        eprintln!(
            "cargo:warning=  No variadic macro support defines found - checking if this is correct"
        );
        // Check if content contains any mention of variadic macros
        if content.contains("VARIADIC") {
            eprintln!("cargo:warning=  Content mentions VARIADIC but defines not found");
        }
    }

    // NGX_MODULE_SIGNATURE_17: Always "0"
    flags.push('0');

    // NGX_MODULE_SIGNATURE_18: Check nginx source to determine actual logic
    // Different nginx versions may have different definitions
    // Try to read from nginx source ngx_module.h if available
    let sig18 = if let Ok(nginx_source_dir) = env::var("NGINX_SOURCE_DIR") {
        let ngx_module_h = PathBuf::from(&nginx_source_dir).join("src/core/ngx_module.h");
        if ngx_module_h.exists() {
            if let Ok(module_h_content) = fs::read_to_string(&ngx_module_h) {
                // Check if NGX_MODULE_SIGNATURE_18 is hardcoded as "0"
                if module_h_content.contains("#define NGX_MODULE_SIGNATURE_18  \"0\"") {
                    '0'
                } else if module_h_content.contains("NGX_MODULE_SIGNATURE_18") {
                    // If it's conditionally defined, check the condition
                    // For now, default to checking NGX_QUIC || NGX_COMPAT if not hardcoded
                    if has_define(content, "NGX_QUIC") || has_define(content, "NGX_COMPAT") {
                        '1'
                    } else {
                        '0'
                    }
                } else {
                    // Fallback: check NGX_QUIC || NGX_COMPAT
                    if has_define(content, "NGX_QUIC") || has_define(content, "NGX_COMPAT") {
                        '1'
                    } else {
                        '0'
                    }
                }
            } else {
                // Fallback: check NGX_QUIC || NGX_COMPAT
                if has_define(content, "NGX_QUIC") || has_define(content, "NGX_COMPAT") {
                    '1'
                } else {
                    '0'
                }
            }
        } else {
            // Fallback: check NGX_QUIC || NGX_COMPAT
            if has_define(content, "NGX_QUIC") || has_define(content, "NGX_COMPAT") {
                '1'
            } else {
                '0'
            }
        }
    } else {
        // Fallback: check NGX_QUIC || NGX_COMPAT
        if has_define(content, "NGX_QUIC") || has_define(content, "NGX_COMPAT") {
            '1'
        } else {
            '0'
        }
    };
    flags.push(sig18);

    // NGX_MODULE_SIGNATURE_19: NGX_HAVE_OPENAT
    flags.push(if has_define(content, "NGX_HAVE_OPENAT") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_20: NGX_HAVE_ATOMIC_OPS
    // This is defined in src/os/unix/ngx_atomic.h, not in ngx_auto_config.h
    // We read ngx_atomic.h earlier and added it to config_content
    // However, the define might be conditional or might not exist on all platforms
    let has_atomic_ops = has_define(content, "NGX_HAVE_ATOMIC_OPS");
    let sig20 = if has_atomic_ops { '1' } else { '0' };
    flags.push(sig20);
    eprintln!(
        "cargo:warning=NGX_MODULE_SIGNATURE_20 (atomic ops): {} (found in content: {})",
        sig20, has_atomic_ops
    );

    // Debug: check if ngx_atomic.h was read
    if content.contains("NGX_HAVE_ATOMIC_OPS") {
        eprintln!("cargo:warning=  NGX_HAVE_ATOMIC_OPS found in content (from ngx_atomic.h)");
    } else {
        eprintln!("cargo:warning=  NGX_HAVE_ATOMIC_OPS not found in content - atomic ops may not be available");
        eprintln!("cargo:warning=  This is normal on some platforms (e.g., older Linux kernels)");
    }

    // NGX_MODULE_SIGNATURE_21: NGX_HAVE_POSIX_SEM
    flags.push(if has_define(content, "NGX_HAVE_POSIX_SEM") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_22: NGX_THREADS || NGX_COMPAT
    flags.push(
        if has_define(content, "NGX_THREADS") || has_define(content, "NGX_COMPAT") {
            '1'
        } else {
            '0'
        },
    );

    // NGX_MODULE_SIGNATURE_23: NGX_PCRE
    flags.push(if has_define(content, "NGX_PCRE") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_24: NGX_HTTP_SSL || NGX_COMPAT
    flags.push(
        if has_define(content, "NGX_HTTP_SSL") || has_define(content, "NGX_COMPAT") {
            '1'
        } else {
            '0'
        },
    );

    // NGX_MODULE_SIGNATURE_25: Always "1"
    flags.push('1');

    // NGX_MODULE_SIGNATURE_26: NGX_HTTP_GZIP
    flags.push(if has_define(content, "NGX_HTTP_GZIP") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_27: Always "1"
    flags.push('1');

    // NGX_MODULE_SIGNATURE_28: NGX_HTTP_X_FORWARDED_FOR
    flags.push(if has_define(content, "NGX_HTTP_X_FORWARDED_FOR") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_29: NGX_HTTP_REALIP
    flags.push(if has_define(content, "NGX_HTTP_REALIP") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_30: NGX_HTTP_HEADERS
    flags.push(if has_define(content, "NGX_HTTP_HEADERS") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_31: NGX_HTTP_DAV
    flags.push(if has_define(content, "NGX_HTTP_DAV") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_32: NGX_HTTP_CACHE
    flags.push(if has_define(content, "NGX_HTTP_CACHE") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_33: NGX_HTTP_UPSTREAM_ZONE
    flags.push(if has_define(content, "NGX_HTTP_UPSTREAM_ZONE") {
        '1'
    } else {
        '0'
    });

    // NGX_MODULE_SIGNATURE_34: NGX_COMPAT
    flags.push(if has_define(content, "NGX_COMPAT") {
        '1'
    } else {
        '0'
    });

    flags
}

/// Check if a define exists in the content (either as #define NAME or #define NAME value).
///
/// This function ensures exact matching to avoid prefix issues. For example, when checking
/// for `NGX_HAVE_EPOLL`, it won't incorrectly match `NGX_HAVE_EPOLLEXCLUSIVE`.
fn has_define(content: &str, name: &str) -> bool {
    let define_pattern = format!("#define {}", name);
    content.lines().any(|line| {
        let trimmed = line.trim();
        // Match "#define NAME" exactly (no value)
        if trimmed == define_pattern {
            return true;
        }
        // Match "#define NAME value" - ensure next char after name is whitespace
        if trimmed.starts_with(&define_pattern) {
            let remaining = &trimmed[define_pattern.len()..];
            // Next character must be whitespace (space or tab) or end of line
            return remaining.is_empty()
                || remaining.starts_with(' ')
                || remaining.starts_with('\t');
        }
        false
    })
}

/// Extract a numeric value from a #define directive.
///
/// Example: `#define NGX_PTR_SIZE 8` -> Some(8)
///
/// This function ensures exact matching to avoid prefix issues. For example, when checking
/// for `NGX_HAVE_EPOLL`, it won't incorrectly match `NGX_HAVE_EPOLLEXCLUSIVE`.
fn extract_define_value(content: &str, name: &str) -> Option<u32> {
    let define_pattern = format!("#define {}", name);

    for line in content.lines() {
        let trimmed = line.trim();
        // Check if line starts with the define pattern
        if trimmed.starts_with(&define_pattern) {
            let remaining = &trimmed[define_pattern.len()..];
            // Ensure next character after name is whitespace (not part of another define name)
            if remaining.is_empty() || remaining.starts_with(' ') || remaining.starts_with('\t') {
                let parts: Vec<&str> = trimmed.split_whitespace().collect();
                if parts.len() >= 3 {
                    if let Ok(value) = parts[2].parse::<u32>() {
                        return Some(value);
                    }
                }
            }
        }
    }

    None
}

/// Extract a string or numeric value from a #define directive.
///
/// Handles both quoted strings and numeric values:
/// - `#define NGX_MODULE_SIGNATURE_1 "0010"` -> Some("0010")
/// - `#define NGX_MODULE_SIGNATURE_1 0010` -> Some("0010")
///
/// This function ensures exact matching to avoid prefix issues. For example, when checking
/// for `NGX_HAVE_EPOLL`, it won't incorrectly match `NGX_HAVE_EPOLLEXCLUSIVE`.
fn extract_define_string(content: &str, name: &str) -> Option<String> {
    let define_pattern = format!("#define {}", name);

    for line in content.lines() {
        let trimmed = line.trim();
        // Check if line starts with the define pattern
        if trimmed.starts_with(&define_pattern) {
            let remaining = &trimmed[define_pattern.len()..];
            // Ensure next character after name is whitespace (not part of another define name)
            if remaining.is_empty() || remaining.starts_with(' ') || remaining.starts_with('\t') {
                // Try to extract quoted string first
                if let Some(start) = trimmed.find('"') {
                    if let Some(end) = trimmed.rfind('"') {
                        if end > start {
                            return Some(trimmed[start + 1..end].to_string());
                        }
                    }
                }

                // Fallback: extract numeric value
                let parts: Vec<&str> = trimmed.split_whitespace().collect();
                if parts.len() >= 3 {
                    return Some(parts[2].to_string());
                }
            }
        }
    }

    None
}

/// Automatically detect nginx version and download source if needed.
///
/// This function:
/// 1. Detects nginx version from system nginx binary
/// 2. Checks common locations for existing nginx source
/// 3. Downloads and configures nginx source if not found
fn auto_download_nginx_source() -> io::Result<PathBuf> {
    // Detect nginx version
    let nginx_version = detect_nginx_version()?;

    // Check common locations for existing nginx source
    // Priority order:
    // 1. System package manager installed source (e.g., nginx-source, nginx-dev)
    // 2. Common build directories
    // 3. Temporary directories
    let common_paths = [
        // Debian/Ubuntu: nginx-source package
        format!("/usr/src/nginx-{}", nginx_version),
        format!("/usr/src/nginx/nginx-{}", nginx_version),
        // RedHat/CentOS: nginx source RPM
        format!("/usr/src/redhat/BUILD/nginx-{}", nginx_version),
        format!("/usr/src/rpm/BUILD/nginx-{}", nginx_version),
        // Common build locations
        format!("/usr/share/nginx-{}", nginx_version),
        format!("/tmp/nginx-{}", nginx_version),
    ];

    for path_str in &common_paths {
        let path = PathBuf::from(path_str);
        // Check for objs/ngx_auto_config.h as indicator of configured source
        if path.join("objs/ngx_auto_config.h").exists() {
            eprintln!(
                "cargo:warning=Found configured nginx source at: {}",
                path.display()
            );
            return Ok(path);
        }
    }

    // Download and configure nginx source
    download_and_configure_nginx(&nginx_version)
}

/// Detect nginx version from system nginx binary.
fn detect_nginx_version() -> io::Result<String> {
    // Try to get version from nginx -v command
    let output = Command::new("nginx")
        .arg("-v")
        .output()
        .map_err(|e| io::Error::other(format!("nginx command not found: {}", e)))?;

    let output_str = String::from_utf8_lossy(&output.stderr);

    // Extract version from output like "nginx version: nginx/1.24.0"
    if let Some(version) = extract_version_from_string(&output_str) {
        return Ok(version);
    }

    Err(io::Error::other(format!(
        "Could not detect nginx version from: {}",
        output_str
    )))
}

/// Extract version number from nginx version string.
fn extract_version_from_string(s: &str) -> Option<String> {
    // Look for pattern like "nginx/1.24.0" or "nginx version: nginx/1.24.0"
    for part in s.split_whitespace() {
        if let Some(version) = part.strip_prefix("nginx/") {
            return Some(version.to_string());
        }
    }

    // Fallback: look for version pattern X.Y.Z
    for part in s.split_whitespace() {
        if part.matches('.').count() == 2 {
            let parts: Vec<&str> = part.split('.').collect();
            if parts.len() == 3 && parts.iter().all(|p| p.parse::<u32>().is_ok()) {
                return Some(part.to_string());
            }
        }
    }

    None
}

/// Download and configure nginx source.
fn download_and_configure_nginx(version: &str) -> io::Result<PathBuf> {
    let download_dir = PathBuf::from("/tmp");
    let tarball_name = format!("nginx-{}.tar.gz", version);
    let tarball_path = download_dir.join(&tarball_name);
    let source_dir = download_dir.join(format!("nginx-{}", version));

    // Check if already downloaded and configured
    if source_dir.join("objs/ngx_auto_config.h").exists() {
        return Ok(source_dir);
    }

    // Download nginx source
    let download_url = format!("https://nginx.org/download/{}", tarball_name);

    // Try wget first, then curl
    let download_success = Command::new("wget")
        .args(["-q", "-O", tarball_path.to_str().unwrap(), &download_url])
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
        || Command::new("curl")
            .args(["-sSfL", "-o", tarball_path.to_str().unwrap(), &download_url])
            .status()
            .map(|s| s.success())
            .unwrap_or(false);

    if !download_success {
        return Err(io::Error::other(format!(
            "Failed to download nginx source from {}",
            download_url
        )));
    }

    // Extract tarball
    let extract_success = Command::new("tar")
        .args(["-xzf", tarball_path.to_str().unwrap()])
        .current_dir(&download_dir)
        .status()
        .map(|s| s.success())
        .unwrap_or(false);

    // Clean up tarball
    let _ = fs::remove_file(&tarball_path);

    if !extract_success || !source_dir.exists() {
        return Err(io::Error::other("Failed to extract nginx source"));
    }

    // Configure nginx source
    configure_nginx_source(&source_dir)?;

    Ok(source_dir)
}

/// Configure nginx source with minimal configuration.
fn configure_nginx_source(source_dir: &PathBuf) -> io::Result<()> {
    // Check if already configured (e.g., in Docker build where nginx was pre-configured)
    if source_dir.join("objs/ngx_auto_config.h").exists() {
        eprintln!(
            "cargo:warning=Nginx source already configured at: {}",
            source_dir.display()
        );
        return Ok(());
    }

    // Try to get configure args from system nginx
    let configure_args = get_system_configure_args()
        .unwrap_or_else(|_| "--without-http_rewrite_module --with-cc-opt=-fPIC".to_string());

    eprintln!(
        "cargo:warning=Configuring nginx source with args: {}",
        configure_args
    );

    // Run configure with shell to properly handle quoted arguments
    // Use sh -c to ensure proper argument parsing
    let configure_script = format!(
        "cd {} && ./configure {}",
        source_dir.display(),
        configure_args
    );

    let configure_output = Command::new("sh").arg("-c").arg(&configure_script).output();

    match configure_output {
        Ok(output) if output.status.success() => {
            eprintln!("cargo:warning=Nginx configure succeeded");
        }
        Ok(output) => {
            let stderr = String::from_utf8_lossy(&output.stderr);
            eprintln!(
                "cargo:warning=Nginx configure failed, trying fallback. Error: {}",
                stderr
            );

            // Try with parsed arguments as fallback
            let parsed_args = parse_configure_args(&configure_args);
            let mut cmd = Command::new("./configure");
            cmd.current_dir(source_dir);
            for arg in &parsed_args {
                cmd.arg(arg);
            }

            let parsed_output = cmd.output();
            match parsed_output {
                Ok(output) if output.status.success() => {
                    eprintln!("cargo:warning=Nginx configure succeeded with parsed arguments");
                }
                Ok(output) => {
                    let stderr = String::from_utf8_lossy(&output.stderr);
                    eprintln!(
                        "cargo:warning=Parsed args configure failed, trying minimal. Error: {}",
                        stderr
                    );

                    // Try minimal configuration
                    let minimal_output = Command::new("./configure")
                        .args(["--without-http_rewrite_module", "--with-cc-opt=-fPIC"])
                        .current_dir(source_dir)
                        .output();

                    match minimal_output {
                        Ok(output) if output.status.success() => {
                            eprintln!("cargo:warning=Nginx configure succeeded with minimal args");
                        }
                        Ok(output) => {
                            let stderr = String::from_utf8_lossy(&output.stderr);
                            return Err(io::Error::other(format!(
                                "Failed to configure nginx source: {}",
                                stderr
                            )));
                        }
                        Err(e) => {
                            return Err(io::Error::other(format!(
                                "Failed to run configure: {}",
                                e
                            )));
                        }
                    }
                }
                Err(e) => {
                    return Err(io::Error::other(format!(
                        "Failed to run configure with parsed args: {}",
                        e
                    )));
                }
            }
        }
        Err(e) => {
            return Err(io::Error::other(format!(
                "Failed to run configure script: {}",
                e
            )));
        }
    }

    // Verify configure succeeded
    if !source_dir.join("objs/ngx_auto_config.h").exists() {
        return Err(io::Error::other(
            "Configure appeared to succeed but ngx_auto_config.h not found",
        ));
    }

    Ok(())
}

/// Parse configure arguments handling quoted strings properly.
///
/// This function properly handles arguments like:
/// --with-cc-opt='-g -O2 -fPIC'
/// --with-ld-opt='-Wl,-z,relro'
///
/// Returns a vector of argument strings, preserving quoted strings as single arguments.
fn parse_configure_args(args: &str) -> Vec<String> {
    let mut result = Vec::new();
    let mut current = String::new();
    let mut in_single_quote = false;
    let mut in_double_quote = false;

    for ch in args.chars() {
        match ch {
            '\'' if !in_double_quote => {
                in_single_quote = !in_single_quote;
                current.push(ch);
            }
            '"' if !in_single_quote => {
                in_double_quote = !in_double_quote;
                current.push(ch);
            }
            ' ' | '\t' if !in_single_quote && !in_double_quote => {
                if !current.is_empty() {
                    result.push(current.clone());
                    current.clear();
                }
            }
            _ => {
                current.push(ch);
            }
        }
    }

    if !current.is_empty() {
        result.push(current);
    }

    result
}

/// Get configure arguments from system nginx.
fn get_system_configure_args() -> io::Result<String> {
    let output = Command::new("nginx")
        .arg("-V")
        .output()
        .map_err(|e| io::Error::other(format!("nginx command not found: {}", e)))?;

    let output_str = String::from_utf8_lossy(&output.stderr);

    // Extract configure arguments
    if let Some(args_start) = output_str.find("configure arguments:") {
        let args = &output_str[args_start + "configure arguments:".len()..];
        // Clean up the arguments (remove problematic modules)
        let cleaned = clean_configure_args(args.trim());
        return Ok(cleaned);
    }

    Err(io::Error::other(
        "Could not extract configure arguments from nginx -V",
    ))
}

/// Clean configure arguments by removing problematic modules.
///
/// IMPORTANT: We preserve all arguments that affect binary compatibility:
/// - --with-cc-opt (compiler flags)
/// - --with-ld-opt (linker flags)
/// - --prefix, --conf-path, etc. (paths don't affect signature)
/// - Feature flags (--with-*, --without-*) that affect module signature
///
/// We only remove modules that cause build issues but don't affect signature.
///
/// This function uses simple string replacement but is careful to only remove
/// standalone module arguments, not ones that might be inside quoted strings.
fn clean_configure_args(args: &str) -> String {
    let mut cleaned = args.to_string();

    // Modules that cause build issues but don't affect signature
    // These can be safely removed, but we need to be careful about quoted strings
    // We only remove them if they appear as standalone arguments (preceded by space or start of string,
    // followed by space or end of string)
    let modules_to_remove = [
        " --with-http_rewrite_module ",
        " --with-http_xslt_module=dynamic ",
        " --with-http_perl_module=dynamic ",
        " --with-http_image_filter_module=dynamic ",
        " --with-http_geoip_module=dynamic ",
        " --with-mail=dynamic ",
        " --with-stream=dynamic ",
        " --with-stream_geoip_module=dynamic ",
    ];

    // Also handle cases at start/end of string
    for module in &modules_to_remove {
        let trimmed = module.trim();
        // Remove from middle of string
        cleaned = cleaned.replace(module, " ");
        // Remove from start
        if cleaned.starts_with(trimmed) {
            cleaned = cleaned.replacen(trimmed, "", 1);
        }
        // Remove from end
        if cleaned.ends_with(trimmed) {
            let len = cleaned.len();
            let trim_len = trimmed.len();
            cleaned = cleaned[..len - trim_len].to_string();
        }
    }

    // Add --without-http_rewrite_module if not present
    if !cleaned.contains("--without-http_rewrite_module") {
        cleaned.push_str(" --without-http_rewrite_module");
    }

    // Clean up multiple spaces
    while cleaned.contains("  ") {
        cleaned = cleaned.replace("  ", " ");
    }

    cleaned.trim().to_string()
}

/// Extract nginx module signature directly from nginx binary file.
///
/// Uses the `strings` command to extract the signature string from the binary.
/// Signature format: "^[0-9]+,[0-9]+,[0-9]+,[01]+$"
///
/// This is useful when building against system-installed nginx where we want
/// to match the exact signature of the system nginx binary.
fn extract_signature_from_binary(binary_path: &PathBuf) -> io::Result<String> {
    // Use strings command to extract signature
    let output = Command::new("strings")
        .arg(binary_path)
        .output()
        .map_err(|e| {
            io::Error::other(format!(
                "Failed to run 'strings' command: {}. \
                 Please ensure 'strings' command is available (usually in binutils package)",
                e
            ))
        })?;

    if !output.status.success() {
        return Err(io::Error::other(format!(
            "strings command failed: {}",
            String::from_utf8_lossy(&output.stderr)
        )));
    }

    let output_str = String::from_utf8_lossy(&output.stdout);

    // Look for signature pattern: ^[0-9]+,[0-9]+,[0-9]+,[01]+$
    // This matches the format stored in nginx binary
    for line in output_str.lines() {
        let trimmed = line.trim();
        // Match signature pattern: digits,comma,digits,comma,digits,comma,binary_flags
        if trimmed.starts_with(|c: char| c.is_ascii_digit()) && trimmed.matches(',').count() == 3 {
            // Verify format: N,N,N,01...
            let parts: Vec<&str> = trimmed.split(',').collect();
            if parts.len() == 4 {
                // Verify first three parts are digits
                if parts[0].parse::<u32>().is_ok()
                    && parts[1].parse::<u32>().is_ok()
                    && parts[2].parse::<u32>().is_ok()
                {
                    // Verify fourth part is binary flags (only 0 and 1)
                    if parts[3].chars().all(|c| c == '0' || c == '1') && !parts[3].is_empty() {
                        eprintln!("cargo:warning=Found signature in binary: {}", trimmed);
                        return Ok(trimmed.to_string());
                    }
                }
            }
        }
    }

    Err(io::Error::other(format!(
        "Could not find nginx module signature in binary: {}\n\
         Signature pattern not found. Make sure this is a valid nginx binary.",
        binary_path.display()
    )))
}
