enhance this program further. add actual concurrency control. No multi progress bars, just regular ones.
Advance everything to a next level.
use clap::{Arg, ArgAction, Command as ClapCommand};
use colored::*;
use config::{Config, File};
use futures::stream::{FuturesUnordered, StreamExt};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use itertools::Itertools;
use prettytable::{row, Table};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use reqwest::StatusCode;
use serde::Deserialize;
use serde::Serialize;
use std::{
    collections::{HashMap, HashSet},
    fs,
    io::{self, BufRead},
    path::Path,
    sync::Arc,
};
use thiserror::Error;
use tokio::sync::Semaphore;
use tracing::{debug, error, info, warn};
use tracing_subscriber::fmt::{self, format::FmtSpan};

// Custom Types and Structures

#[derive(Debug, Serialize)]
struct ChannelInfo {
    #[serde(skip_serializing)]
    colored_channel: ColoredString,
    #[serde(rename = "channel")]
    original_channel: String,
    roblox_exe: bool,
    deploy_history: bool,
    mac_deploy_history: bool,
    client_settings: bool,
}

#[derive(Debug, Serialize)]
struct Summary {
    total_combinations: usize,
    valid_channels: usize,
    invalid_channels: usize,
}

#[derive(Debug, Error)]
enum AppError {
    #[error("IO error occurred: {0}")]
    Io(#[from] io::Error),
    #[error("HTTP request error: {0}")]
    Http(#[from] reqwest::Error),
    #[error("Serialization error: {0}")]
    Serde(#[from] serde_json::Error),
    #[error("Configuration error: {0}")]
    Config(#[from] config::ConfigError),
    #[error("CSV error: {0}")]
    Csv(#[from] csv::Error),
    #[error("YAML serialization error: {0}")]
    Yaml(#[from] serde_yaml::Error),
}

#[derive(Debug, Deserialize)]
struct Settings {
    input_files: Option<Vec<String>>,
    output: Option<String>,
    concurrency: usize,
    timeout: u64,
    verbosity: u64,
    prefixes: Option<Vec<String>>,
    suffixes: Option<Vec<String>>,
    random_strings: Option<usize>,
    max_combination_length: usize,
    retry_attempts: usize,
    retry_base_delay_ms: u64,
    rate_limit_per_sec: usize,
    output_format: String, // json, csv, yaml
    templates: Option<Vec<String>>, // New field for templates
}

impl TryFrom<Config> for Settings {
    type Error = AppError;

    fn try_from(config: Config) -> Result<Self, Self::Error> {
        config.try_deserialize().map_err(AppError::from)
    }
}

impl Settings {
    fn new(args: &clap::ArgMatches) -> Result<Self, AppError> {
        // Initialize default configuration
        let mut settings = Config::default();

        // Merge configuration from file if provided
        if let Some(config_path) = args.get_one::<String>("config") {
            settings.merge(File::with_name(config_path))?;
        }

        // Override with command-line arguments
        settings.merge(config::Environment::with_prefix("APP"))?;

        // Deserialize into Settings struct
        let cfg: Settings = settings.try_into()?;
        Ok(cfg)
    }
}

// Custom Logger Initialization

fn init_logger(verbosity: u64) {
    let log_level = match verbosity {
        0 => "info",
        1 => "debug",
        2 => "trace",
        _ => "trace",
    };

    tracing_subscriber::fmt()
        .with_max_level(log_level.parse().unwrap_or(tracing::Level::INFO))
        .with_span_events(FmtSpan::CLOSE)
        .with_target(false)
        .with_timer(fmt::time::ChronoLocal::rfc_3339())
        .init();

    info!(
        "[ 🌃 ] intelligence : Logger initialized with level {}",
        log_level
    );
}

// Wordlist Handling Functions

async fn read_wordlist_from_file(path: &str) -> Result<Vec<String>, AppError> {
    info!("[ 🌃 ] intelligence : Reading wordlist from file: {}", path);
    let file = fs::File::open(path)?;
    let reader = io::BufReader::new(file);
    let wordlist = reader
        .lines()
        .filter_map(|line| line.ok())
        .map(|line| line.trim().to_string())
        .filter(|line| !line.is_empty())
        .collect::<Vec<String>>();
    Ok(wordlist)
}

async fn read_wordlist_from_stdin() -> Result<Vec<String>, AppError> {
    info!("[ 🌃 ] intelligence : Reading wordlist from standard input");
    let stdin = io::stdin();
    let wordlist = stdin
        .lock()
        .lines()
        .filter_map(|line| line.ok())
        .map(|line| line.trim().to_string())
        .filter(|line| !line.is_empty())
        .collect::<Vec<String>>();
    Ok(wordlist)
}

fn augment_wordlist(
    wordlist: &mut Vec<String>,
    prefixes: &[String],
    suffixes: &[String],
    num_random: usize,
) {
    info!(
        "[ 🌃 ] intelligence : Augmenting wordlist with prefixes, suffixes, and random strings"
    );

    let mut augmented = wordlist.clone();

    // Add prefixes
    if !prefixes.is_empty() {
        let prefixed = prefixes
            .iter()
            .cartesian_product(wordlist.iter())
            .map(|(p, w)| format!("{}{}", p, w))
            .collect::<Vec<String>>();
        augmented.extend(prefixed);
    }

    // Add suffixes
    if !suffixes.is_empty() {
        let suffixed = suffixes
            .iter()
            .cartesian_product(wordlist.iter())
            .map(|(s, w)| format!("{}{}", w, s))
            .collect::<Vec<String>>();
        augmented.extend(suffixed);
    }

    // Add random strings
    if num_random > 0 {
        let mut rng = thread_rng();
        let random_strings: Vec<String> = (0..num_random)
            .map(|_| {
                rng.clone().sample_iter(&Alphanumeric)
                    .take(8)
                    .map(char::from)
                    .collect()
            })
            .collect();
        augmented.extend(random_strings);
    }

    // Deduplicate and sort
    let unique_wordlist: HashSet<String> = augmented.into_iter().collect();
    let mut unique_vec: Vec<String> = unique_wordlist.into_iter().collect();
    unique_vec.sort();
    wordlist.clear();
    wordlist.extend(unique_vec);

    info!(
        "[ 🌃 ] intelligence : Wordlist augmented. Total unique words: {}",
        wordlist.len()
    );
}

// Pattern-Based Combination Generation

fn load_templates(settings: &Settings) -> Vec<String> {
    // Define default templates if none are provided
    if let Some(tmpl) = &settings.templates {
        tmpl.clone()
    } else {
        vec![
            "z{{word1}}{{word2}}".to_string(),
            "z{{word1}}{{word2}}{{number}}".to_string(),
            "z{{word1}}{{word2}}testing".to_string(),
            "z{{word1}}".to_string(),
            "z{{word1}}{{number}}".to_string(),
            "z{{word1}}testing".to_string(),
            // Add more templates as needed
        ]
    }
}

fn generate_channels_from_templates(
    wordlist: &[String],
    patterns: &[String],
    max_length: usize,
) -> HashSet<String> {
    info!("[ 🌃 ] intelligence : Generating channels using templates");
    let mut channels = HashSet::new();

    // Create bigrams and trigrams for more natural combinations
    let bigrams: Vec<(String, String)> = wordlist
        .iter()
        .combinations(2)
        .map(|combo| (combo[0].clone(), combo[1].clone()))
        .collect();

    let trigrams: Vec<(String, String, String)> = wordlist
        .iter()
        .combinations(3)
        .map(|combo| (combo[0].clone(), combo[1].clone(), combo[2].clone()))
        .collect();

    // Generate channels based on templates
    for pattern in patterns {
        for word1 in wordlist {
            for word2 in wordlist {
                if pattern.contains("{{word3}}") && max_length >= 3 {
                    for word3 in wordlist {
                        let channel = pattern
                            .replace("{{word1}}", word1)
                            .replace("{{word2}}", word2)
                            .replace("{{word3}}", word3);
                        // Replace number placeholder if present
                        if channel.contains("{{number}}") {
                            for number in 0..10 {
                                let temp_channel = channel.replace("{{number}}", &number.to_string());
                                channels.insert(temp_channel.clone());
                            }
                        } else {
                            channels.insert(channel.clone());
                        }
                    }
                } else {
                    let channel = pattern
                        .replace("{{word1}}", word1)
                        .replace("{{word2}}", word2);
                    // Replace number placeholder if present
                    if channel.contains("{{number}}") {
                        for number in 0..10 {
                            let temp_channel = channel.replace("{{number}}", &number.to_string());
                            channels.insert(temp_channel.clone());
                        }
                    } else {
                        channels.insert(channel.clone());
                    }
                }
            }
        }
    }

    // Optionally, include random number variations
    // This can be expanded based on requirements

    info!(
        "[ 🌃 ] intelligence : Total channels generated from templates: {}",
        channels.len()
    );

    channels
}

// Channel Checking Function

async fn check_channel(
    client: &reqwest::Client,
    channel_name: &str,
    retry_attempts: usize,
    base_delay_ms: u64,
) -> Option<ChannelInfo> {
    let base_url = format!("http://setup.rbxcdn.com/channel/z{}", channel_name);
    let endpoints = [
        ("Roblox.exe", format!("{}/Roblox.exe", base_url)),
        ("DeployHistory.txt", format!("{}/DeployHistory.txt", base_url)),
        (
            "Mac DeployHistory.txt",
            format!("{}/mac/DeployHistory.txt", base_url),
        ),
        (
            "ClientSettings",
            format!(
                "http://clientsettings.roblox.com/v2/settings/application/RCCBootstrapper/bucket/z{}",
                channel_name
            ),
        ),
    ];

    let mut results = vec![];

    for (name, url) in &endpoints {
        let mut attempt = 0;
        loop {
            attempt += 1;
            match client.get(url).send().await {
                Ok(response) => {
                    if response.status() == StatusCode::OK {
                        if *name == "ClientSettings" {
                            match response.text().await {
                                Ok(body) => {
                                    results.push(!body.contains(r#"{"applicationSettings":null}"#));
                                }
                                Err(e) => {
                                    warn!(
                                        "[ 🌃 ] intelligence : Failed to read body for {}: {}",
                                        url, e
                                    );
                                    results.push(false);
                                }
                            }
                        } else {
                            results.push(true);
                        }
                        break;
                    } else {
                        results.push(false);
                        break;
                    }
                }
                Err(e) => {
                    if attempt >= retry_attempts {
                        error!(
                            "[ 🌃 ] intelligence : Failed to fetch {} after {} attempts: {}",
                            url, attempt, e
                        );
                        results.push(false);
                        break;
                    } else {
                        warn!(
                            "[ 🌃 ] intelligence : Error fetching {}: {}. Retrying... (attempt {})",
                            url, e, attempt
                        );
                        tokio::time::sleep(
                            tokio::time::Duration::from_millis(base_delay_ms * 2_u64.pow(attempt.try_into().unwrap())),
                        )
                        .await;
                        continue;
                    }
                }
            }
        }
    }

    if results[0] {
        Some(ChannelInfo {
            colored_channel: color_code_channel(channel_name),
            original_channel: channel_name.to_string(),
            roblox_exe: results[0],
            deploy_history: results[1],
            mac_deploy_history: results[2],
            client_settings: results[3],
        })
    } else {
        None
    }
}

fn color_code_channel(channel: &str) -> ColoredString {
    if channel.contains("feature")
        || channel.contains("project")
        || channel.contains("bugfix")
        || channel.contains("hotfix")
    {
        channel.bold().blue()
    } else if channel.contains("release") || channel.contains("stable") {
        channel.bold().green()
    } else {
        channel.red()
    }
}

// Reporting Functions

fn display_results(results: &[ChannelInfo]) {
    info!("[ 🌃 ] intelligence : Displaying results in table format");

    let mut table = Table::new();
    table.add_row(row![
        "Channel",
        "Roblox.exe",
        "DeployHistory.txt",
        "Mac DeployHistory.txt",
        "ClientSettings",
    ]);

    for info in results {
        table.add_row(row![
            info.colored_channel,
            status_to_mark(info.roblox_exe),
            status_to_mark(info.deploy_history),
            status_to_mark(info.mac_deploy_history),
            status_to_mark(info.client_settings),
        ]);
    }

    table.printstd();
}

fn status_to_mark(status: bool) -> ColoredString {
    if status {
        "✅".green()
    } else {
        "❌".red()
    }
}

fn save_results(
    results: &[ChannelInfo],
    summary: &Summary,
    path: &str,
    format: &str,
) -> Result<(), AppError> {
    info!("[ 🌃 ] intelligence : Saving results to {}", path);

    match format.to_lowercase().as_str() {
        "json" => {
            let output = serde_json::json!({
                "summary": summary,
                "results": results,
            });
            fs::write(path, serde_json::to_string_pretty(&output)?)?;
        }
        "csv" => {
            let mut wtr = csv::Writer::from_path(path)?;
            wtr.write_record(&[
                "Channel",
                "Roblox.exe",
                "DeployHistory.txt",
                "Mac DeployHistory.txt",
                "ClientSettings",
            ])?;
            for info in results {
                wtr.write_record(&[
                    &info.original_channel,
                    &info.roblox_exe.to_string(),
                    &info.deploy_history.to_string(),
                    &info.mac_deploy_history.to_string(),
                    &info.client_settings.to_string(),
                ])?;
            }
            wtr.flush()?;
        }
        "yaml" => {
            let output = serde_yaml::to_string(&serde_json::json!({
                "summary": summary,
                "results": results,
            }))?;
            fs::write(path, output)?;
        }
        _ => {
            warn!(
                "[ 🌃 ] intelligence : Unsupported output format '{}'. Defaulting to JSON.",
                format
            );
            let output = serde_json::json!({
                "summary": summary,
                "results": results,
            });
            fs::write(path, serde_json::to_string_pretty(&output)?)?;
        }
    }

    Ok(())
}

// Main Execution Function

#[tokio::main]
async fn main() -> Result<(), AppError> {
    // Define command-line arguments with Clap
    let matches = ClapCommand::new("Roblox Channel Checker Plus")
        .version("2.0")
        .author("glass")
        .about("Advanced checker for valid Roblox channels based on wordlist combinations")
        .arg(
            Arg::new("config")
                .short('f')
                .long("config")
                .value_name("FILE")
                .help("Sets a custom configuration file")
        )
        .arg(
            Arg::new("input_files")
                .short('i')
                .long("input")
                .value_name("FILES")
                .help("Sets input wordlist files")
                .action(ArgAction::Append)
        )
        .arg(
            Arg::new("output")
                .short('o')
                .long("output")
                .value_name("FILE")
                .help("Sets an output file to save results")
        )
        .arg(
            Arg::new("concurrency")
                .short('c')
                .long("concurrency")
                .value_name("NUMBER")
                .help("Sets the level of concurrency for HTTP requests")
                .default_value("8"),
        )
        .arg(
            Arg::new("timeout")
                .short('t')
                .long("timeout")
                .value_name("SECONDS")
                .help("Sets the timeout for HTTP requests in seconds")
                .default_value("10"),
        )
        .arg(
            Arg::new("verbosity")
                .short('v')
                .long("verbose")
                .help("Increases verbosity level")
                .action(ArgAction::Count),
        )
        .arg(
            Arg::new("prefixes")
                .short('p')
                .long("prefixes")
                .value_name("PREFIXES")
                .help("Comma-separated list of prefixes to add to words")
        )
        .arg(
            Arg::new("suffixes")
                .short('s')
                .long("suffixes")
                .value_name("SUFFIXES")
                .help("Comma-separated list of suffixes to add to words")
        )
        .arg(
            Arg::new("random_strings")
                .short('r')
                .long("random-strings")
                .value_name("NUMBER")
                .help("Number of random alphanumeric strings to generate and add to the wordlist")
                .default_value("0"),
        )
        .arg(
            Arg::new("max_combination_length")
                .short('m')
                .long("max-combination-length")
                .value_name("NUMBER")
                .help("Maximum length of word combinations")
                .default_value("3"),
        )
        .arg(
            Arg::new("retry_attempts")
                .long("retry-attempts")
                .value_name("NUMBER")
                .help("Number of retry attempts for failed HTTP requests")
                .default_value("3"),
        )
        .arg(
            Arg::new("retry_base_delay_ms")
                .long("retry-delay")
                .value_name("MILLISECONDS")
                .help("Base delay in ms for retries with exponential backoff")
                .default_value("500"),
        )
        .arg(
            Arg::new("rate_limit_per_sec")
                .long("rate-limit")
                .value_name("NUMBER")
                .help("Number of allowed requests per second")
                .default_value("50"),
        )
        .arg(
            Arg::new("output_format")
                .long("output-format")
                .value_name("FORMAT")
                .help("Output format: json, csv, yaml")
                .default_value("json"),
        )
        .arg(
            Arg::new("templates")
                .long("templates")
                .value_name("TEMPLATES")
                .help("Comma-separated list of templates for channel generation")
        )
        .get_matches();

    // Initialize configuration
    let settings = Settings::new(&matches)?;

    // Initialize logging
    init_logger(settings.verbosity);

    // Read and prepare wordlist
    let mut wordlist = Vec::new();

    if let Some(files) = &settings.input_files {
        for file in files {
            let words = read_wordlist_from_file(file).await?;
            wordlist.extend(words);
        }
    } else {
        let words = read_wordlist_from_stdin().await?;
        wordlist.extend(words);
    }

    // Augment wordlist
    let prefixes = settings
        .prefixes.clone()
        .unwrap_or_else(|| vec![])
        .iter()
        .map(|s| s.to_string())
        .collect::<Vec<String>>();
    let suffixes = settings
        .suffixes.clone()
        .unwrap_or_else(|| vec![])
        .iter()
        .map(|s| s.to_string())
        .collect::<Vec<String>>();
    let random_strings = settings.random_strings.unwrap_or(0);

    augment_wordlist(&mut wordlist, &prefixes, &suffixes, random_strings);

    if wordlist.is_empty() {
        error!("[ 🌃 ] intelligence : Wordlist is empty. Please provide at least one word.");
        return Ok(());
    }

    // Generate combinations using templates
    let templates = load_templates(&settings);
    let combinations = generate_channels_from_templates(&wordlist, &templates, settings.max_combination_length);
    let total_combinations = combinations.len();

    // Initialize HTTP client
    let client = reqwest::Client::builder()
        .timeout(tokio::time::Duration::from_secs(settings.timeout))
        .build()?;

    // Initialize rate limiter
    let semaphore = Arc::new(Semaphore::new(settings.rate_limit_per_sec));

    // Initialize progress bars
    let pb = ProgressBar::new(total_combinations as u64);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
            .unwrap_or_else(|_| ProgressStyle::default_bar())
            .progress_chars("#>-"),
    );

    // Initialize result storage
    let results = Arc::new(tokio::sync::Mutex::new(Vec::new()));

    // Initialize a futures unordered collection
    let futures = combinations.into_iter().map(|combination| {
        let client = client.clone();
        let semaphore = semaphore.clone();
        let results = results.clone();
        let pb = pb.clone();
        let retry_attempts = settings.retry_attempts;
        let retry_base_delay_ms = settings.retry_base_delay_ms;

        tokio::spawn(async move {
            let _permit = semaphore.acquire().await;
            if let Some(info) = check_channel(
                &client,
                &combination,
                retry_attempts,
                retry_base_delay_ms,
            )
            .await
            {
                results.lock().await.push(info);
            }
            pb.inc(1);
        })
    });

    // Execute all futures concurrently with rate limiting
    let mut fut_unordered = FuturesUnordered::from_iter(futures);

    {
        // Await all tasks
        while fut_unordered.next().await.is_some() {}
    } // All references to `results` are dropped here

    // Gather results
    let results = Arc::try_unwrap(results)
        .expect("Arc has multiple owners")
        .into_inner();

    // Display results
    if !results.is_empty() {
        display_results(&results);
    } else {
        info!("[ 🌃 ] intelligence : No valid channels found.");
    }

    // Prepare summary
    let summary = Summary {
        total_combinations,
        valid_channels: results.len(),
        invalid_channels: total_combinations - results.len(),
    };

    // Save results if output path is provided
    if let Some(output_path) = &settings.output {
        save_results(&results, &summary, output_path, &settings.output_format)?;
    }

    // Display summary
    info!("[ 🌃 ] intelligence : Execution Summary:");
    println!("Total Combinations Generated: {}", summary.total_combinations);
    println!("Valid Channels Found: {}", summary.valid_channels);
    println!("Invalid Channels: {}", summary.invalid_channels);

    Ok(())
}