/*
 * BestCDN High-Performance IP Accessibility Tester
 * C++ implementation for maximum performance
 */

#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <atomic>
#include <chrono>
#include <fstream>
#include <sstream>
#include <queue>
#include <condition_variable>
#include <curl/curl.h>
#include <json/json.h>
#include <future>
#include <algorithm>
#include <cstdint>
#include <map>

struct TestResult {
    std::string ip;
    std::string provider;
    bool accessible;
    double response_time;
    int status_code;
    std::string error;
    std::vector<std::string> successful_protocols;  // Store all working protocols
    
    TestResult() : accessible(false), response_time(0.0), status_code(0) {}
};

struct HTTPResponse {
    std::string data;
    long status_code;
    double response_time;
    
    HTTPResponse() : status_code(0), response_time(0.0) {}
};

class BestCDNTester {
private:
    std::mutex results_mutex;
    std::mutex live_file_mutex;
    std::vector<TestResult> results;
    std::atomic<int> completed_tests{0};
    std::atomic<int> successful_tests{0};
    std::ofstream live_results_file;
    
    // Configuration
    int concurrent_requests = 100;
    double request_timeout = 3.0;
    int max_retries = 2;
    
    // Test domains
    std::map<std::string, std::string> test_domains = {
        {"edgeone", "test10000.ix.je"},
        {"esa", "test10000.ix.je"},
        {"fastly", "test10000.ix.je"},
        {"cloudflare", "test10000.fstring.me"}
    };
    
    static size_t WriteCallback(void* contents, size_t size, size_t nmemb, HTTPResponse* response) {
        size_t total_size = size * nmemb;
        response->data.append((char*)contents, total_size);
        return total_size;
    }
    
    HTTPResponse performHTTPRequest(const std::string& url, const std::string& host_header) {
        HTTPResponse response;
        CURL* curl = curl_easy_init();
        
        if (!curl) {
            return response;
        }
        
        // Set URL
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        
        // Set Host header
        struct curl_slist* headers = nullptr;
        if (!host_header.empty()) {
            std::string host_line = "Host: " + host_header;
            headers = curl_slist_append(headers, host_line.c_str());
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        }
        
        // Set User-Agent
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15/1.0");
        
        // Set callback for response data
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
        
        // Set timeout
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)request_timeout);
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2L);
        
        // Disable SSL verification for speed
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
        
        // Don't follow redirects
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
        
        // Perform request
        auto start_time = std::chrono::high_resolution_clock::now();
        CURLcode res = curl_easy_perform(curl);
        auto end_time = std::chrono::high_resolution_clock::now();
        
        response.response_time = std::chrono::duration<double>(end_time - start_time).count();
        
        if (res == CURLE_OK) {
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.status_code);
        }
        
        // Cleanup
        if (headers) {
            curl_slist_free_all(headers);
        }
        curl_easy_cleanup(curl);
        
        return response;
    }
    
    bool verifyJSONResponse(const std::string& json_data) {
        Json::Value root;
        Json::Reader reader;
        
        if (!reader.parse(json_data, root)) {
            return false;
        }
        
        if (!root.isMember("test_message")) {
            return false;
        }
        
        return root["test_message"].asString() == "testpagegalaxy123";
    }
    
    TestResult testSingleIP(const std::string& ip, const std::string& provider) {
        TestResult result;
        result.ip = ip;
        result.provider = provider;
        
        std::string test_domain = test_domains[provider];
        std::vector<std::string> protocols = {"https", "http"};
        
        double total_response_time = 0.0;
        int successful_tests = 0;
        std::string last_error;
        
        // Test both protocols and record all successful ones
        for (const auto& protocol : protocols) {
            std::string url = protocol + "://" + ip + "/testpage.php";
            
            HTTPResponse response = performHTTPRequest(url, test_domain);
            
            if (response.status_code == 200) {
                if (verifyJSONResponse(response.data)) {
                    std::cout<<"POSSIBLE IP "<<ip<<"\n";
                    // This protocol works!
                    result.successful_protocols.push_back(protocol);
                    total_response_time += response.response_time;
                    successful_tests++;
                    result.accessible = true;
                } else {
                    last_error = "Invalid JSON response or wrong test_message";
                }
            } else if (response.status_code > 0) {
                last_error = "HTTP " + std::to_string(response.status_code);
            } else {
                last_error = "Connection failed";
            }
        }
        
        // Set final result properties
        if (successful_tests > 0) {
            result.response_time = total_response_time / successful_tests;  // Average response time
            result.status_code = 200;  // At least one protocol succeeded
            
            // Real-time output of successful IP
            std::cout << "✓ " << ip << " ";
            for (const auto& protocol : result.successful_protocols) {
                std::cout << protocol << " ";
            }
            std::cout << "(" << provider << ")" << std::endl;
            std::cout.flush();  // Ensure immediate output
            
            // Write to live results file
            {
                std::lock_guard<std::mutex> lock(live_file_mutex);
                if (live_results_file.is_open()) {
                    live_results_file << ip;
                    for (const auto& protocol : result.successful_protocols) {
                        live_results_file << " " << protocol;
                    }
                    live_results_file << " (" << provider << ")" << std::endl;
                    live_results_file.flush();
                }
            }
        } else {
            result.accessible = false;
            result.error = last_error;
            result.response_time = 0.0;
            result.status_code = 0;
        }
        
        return result;
    }
    
public:
    BestCDNTester(int concurrent = 100, double timeout = 3.0)
        : concurrent_requests(concurrent), request_timeout(timeout) {
        curl_global_init(CURL_GLOBAL_DEFAULT);
        
        // Initialize live results file
        auto now = std::chrono::system_clock::now();
        auto time_t = std::chrono::system_clock::to_time_t(now);
        auto tm = *std::localtime(&time_t);
        
        char timestamp[20];
        std::strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &tm);
        
        std::string live_filename = "../../results/live_results_" + std::string(timestamp) + ".txt";
        live_results_file.open(live_filename);
        if (live_results_file.is_open()) {
            live_results_file << "# Live BestCDN Results - Real-time successful IPs\n";
            live_results_file << "# Format: IP protocol1 protocol2 ... (provider)\n";
            live_results_file << "# Started: " << std::string(timestamp) << "\n\n";
            live_results_file.flush();
            std::cout << "Live results will be saved to: " << live_filename << std::endl;
        }
    }
    
    ~BestCDNTester() {
        if (live_results_file.is_open()) {
            live_results_file.close();
        }
        curl_global_cleanup();
    }
    
    void testIPBatch(const std::vector<std::pair<std::string, std::string>>& ip_provider_pairs) {
        std::vector<std::future<TestResult>> futures;
        
        // Create thread pool
        for (const auto& pair : ip_provider_pairs) {
            futures.push_back(std::async(std::launch::async, [this, pair]() {
                return testSingleIP(pair.first, pair.second);
            }));
            
            // Limit concurrent requests
            if (futures.size() >= concurrent_requests) {
                // Wait for some to complete
                for (auto& future : futures) {
                    TestResult result = future.get();
                    
                    {
                        std::lock_guard<std::mutex> lock(results_mutex);
                        results.push_back(result);
                    }
                    
                    completed_tests++;
                    if (result.accessible) {
                        successful_tests++;
                    }
                }
                futures.clear();
            }
        }
        
        // Wait for remaining futures
        for (auto& future : futures) {
            TestResult result = future.get();
            
            {
                std::lock_guard<std::mutex> lock(results_mutex);
                results.push_back(result);
            }
            
            completed_tests++;
            if (result.accessible) {
                successful_tests++;
            }
        }
    }
    
    void testAllIPs(const std::map<std::string, std::vector<std::string>>& provider_ips) {
        std::vector<std::pair<std::string, std::string>> all_pairs;
        
        // Flatten all IPs with their providers
        for (const auto& provider_pair : provider_ips) {
            const std::string& provider = provider_pair.first;
            const std::vector<std::string>& ips = provider_pair.second;
            
            for (const std::string& ip : ips) {
                all_pairs.emplace_back(ip, provider);
            }
        }
        
        std::cout << "Testing " << all_pairs.size() << " IPs across " 
                  << provider_ips.size() << " providers..." << std::endl;
        
        auto start_time = std::chrono::high_resolution_clock::now();
        
        // Process in batches
        const size_t batch_size = 1000;
        for (size_t i = 0; i < all_pairs.size(); i += batch_size) {
            size_t end = std::min(i + batch_size, all_pairs.size());
            std::vector<std::pair<std::string, std::string>> batch(
                all_pairs.begin() + i, all_pairs.begin() + end);
            
            std::cout << "Processing batch " << (i / batch_size + 1) 
                      << " (" << batch.size() << " IPs)..." << std::endl;
            
            testIPBatch(batch);
            
            std::cout << "Completed: " << completed_tests.load() 
                      << "/" << all_pairs.size() 
                      << " (Accessible: " << successful_tests.load() << ")" << std::endl;
        }
        
        auto end_time = std::chrono::high_resolution_clock::now();
        double total_time = std::chrono::duration<double>(end_time - start_time).count();
        
        std::cout << "\nTesting completed in " << total_time << " seconds" << std::endl;
        std::cout << "Total accessible: " << successful_tests.load() 
                  << "/" << completed_tests.load() << std::endl;
    }
    
    void saveResults(const std::string& filename) {
        std::ofstream file(filename);
        Json::Value root;
        Json::Value results_array(Json::arrayValue);
        
        for (const auto& result : results) {
            Json::Value result_obj;
            result_obj["ip"] = result.ip;
            result_obj["provider"] = result.provider;
            result_obj["accessible"] = result.accessible;
            result_obj["response_time"] = result.response_time;
            result_obj["status_code"] = result.status_code;
            result_obj["error"] = result.error;
            
            // Add successful protocols array
            Json::Value protocols_array(Json::arrayValue);
            for (const auto& protocol : result.successful_protocols) {
                protocols_array.append(protocol);
            }
            result_obj["successful_protocols"] = protocols_array;
            
            results_array.append(result_obj);
        }
        
        root["results"] = results_array;
        root["total_tested"] = (int)results.size();
        root["total_accessible"] = successful_tests.load();
        
        file << root;
        file.close();
        
        std::cout << "Results saved to " << filename << std::endl;
    }
    
    void saveAccessibleIPs(const std::string& filename) {
        std::ofstream file(filename);
        std::map<std::string, std::vector<TestResult>> accessible_by_provider;
        
        for (const auto& result : results) {
            if (result.accessible) {
                accessible_by_provider[result.provider].push_back(result);
            }
        }
        
        file << "# Accessible CDN IPs with Protocols - Generated by BestCDN C++ Tester\n";
        file << "# Format: IP protocol1 protocol2 ...\n";
        file << "# Total accessible IPs: " << successful_tests.load() << "\n\n";
        
        for (const auto& provider_pair : accessible_by_provider) {
            file << "# " << provider_pair.first << " - "
                 << provider_pair.second.size() << " accessible IPs\n";
            
            for (const auto& result : provider_pair.second) {
                file << result.ip;
                for (const auto& protocol : result.successful_protocols) {
                    file << " " << protocol;
                }
                file << "\n";
            }
            file << "\n";
        }
        
        file.close();
        std::cout << "Accessible IPs saved to " << filename << std::endl;
    }
};

// Function to convert IP string to 32-bit integer
uint32_t ipToInt(const std::string& ip) {
    uint32_t result = 0;
    std::istringstream iss(ip);
    std::string octet;
    int shift = 24;
    
    while (std::getline(iss, octet, '.') && shift >= 0) {
        result |= (std::stoi(octet) << shift);
        shift -= 8;
    }
    
    return result;
}

// Function to convert 32-bit integer to IP string
std::string intToIp(uint32_t ip) {
    return std::to_string((ip >> 24) & 0xFF) + "." +
           std::to_string((ip >> 16) & 0xFF) + "." +
           std::to_string((ip >> 8) & 0xFF) + "." +
           std::to_string(ip & 0xFF);
}

// Function to expand CIDR notation to individual IPs
std::vector<std::string> expandCIDR(const std::string& cidr) {
    std::vector<std::string> ips;
    
    size_t slash_pos = cidr.find('/');
    if (slash_pos == std::string::npos) {
        // Not CIDR, just return the single IP
        ips.push_back(cidr);
        return ips;
    }
    
    std::string network_ip = cidr.substr(0, slash_pos);
    int prefix_length = std::stoi(cidr.substr(slash_pos + 1));
    
    // Validate prefix length
    if (prefix_length < 0 || prefix_length > 32) {
        std::cerr << "Invalid CIDR prefix length: " << prefix_length << std::endl;
        return ips;
    }
    
    uint32_t network = ipToInt(network_ip);
    uint32_t mask = (0xFFFFFFFF << (32 - prefix_length)) & 0xFFFFFFFF;
    uint32_t network_addr = network & mask;
    uint32_t broadcast_addr = network_addr | (~mask & 0xFFFFFFFF);
    
    // Calculate number of host addresses
    uint32_t num_hosts = broadcast_addr - network_addr + 1;
    
    // For IPv4, sample differently based on network size
    if (prefix_length >= 24) {
        // /24 or smaller - include all IPs (skip network and broadcast for /24 and larger)
        uint32_t start = (prefix_length == 32) ? network_addr : network_addr + 1;
        uint32_t end = (prefix_length == 32) ? network_addr : broadcast_addr - 1;
        
        for (uint32_t ip = start; ip <= end; ++ip) {
            ips.push_back(intToIp(ip));
        }
    } else {
        // Larger networks - sample to avoid memory issues
        uint32_t sample_size = std::min(num_hosts, (uint32_t)1000);
        std::cout << "Large CIDR " << cidr << " with " << num_hosts
                  << " IPs - sampling " << sample_size << " IPs" << std::endl;
        
        for (uint32_t i = 0; i < sample_size; ++i) {
            uint32_t ip = network_addr + (i * num_hosts / sample_size);
            ips.push_back(intToIp(ip));
        }
    }
    
    return ips;
}

std::vector<std::string> readIPList(const std::string& filename) {
    std::vector<std::string> ips;
    std::ifstream file(filename);
    std::string line;
    
    if (!file.is_open()) {
        std::cerr << "Error: Cannot open file " << filename << std::endl;
        return ips;
    }
    
    while (std::getline(file, line)) {
        // Remove whitespace
        line.erase(0, line.find_first_not_of(" \t\r\n"));
        line.erase(line.find_last_not_of(" \t\r\n") + 1);
        
        // Skip empty lines and comments
        if (line.empty() || line[0] == '#') {
            continue;
        }
        
        // Handle both single IPs and CIDR notation
        try {
            std::vector<std::string> expanded = expandCIDR(line);
            ips.insert(ips.end(), expanded.begin(), expanded.end());
        } catch (const std::exception& e) {
            std::cerr << "Error processing line '" << line << "': " << e.what() << std::endl;
        }
    }
    
    return ips;
}

int main(int argc, char* argv[]) {
    std::cout << "BestCDN High-Performance C++ Tester" << std::endl;
    std::cout << "====================================" << std::endl;
    
    // Parse command line arguments for provider selection
    std::vector<std::string> selected_providers;
    std::vector<std::string> all_providers = {"cloudflare", "fastly", "edgeone", "esa"};
    
    if (argc > 1) {
        // Use specified providers
        for (int i = 1; i < argc; i++) {
            std::string provider = argv[i];
            // Check if provider is valid
            if (std::find(all_providers.begin(), all_providers.end(), provider) != all_providers.end()) {
                selected_providers.push_back(provider);
            } else {
                std::cerr << "Warning: Unknown provider '" << provider << "'. Available: cloudflare, fastly, edgeone, esa" << std::endl;
            }
        }
        
        if (selected_providers.empty()) {
            std::cerr << "Error: No valid providers specified." << std::endl;
            std::cout << "Usage: " << argv[0] << " [provider1] [provider2] ..." << std::endl;
            std::cout << "Available providers: cloudflare, fastly, edgeone, esa" << std::endl;
            std::cout << "Example: " << argv[0] << " edgeone cloudflare" << std::endl;
            return 1;
        }
    } else {
        // Use all providers if none specified
        selected_providers = all_providers;
        std::cout << "No providers specified, testing all available providers." << std::endl;
    }
    
    std::cout << "Selected providers: ";
    for (size_t i = 0; i < selected_providers.size(); i++) {
        std::cout << selected_providers[i];
        if (i < selected_providers.size() - 1) std::cout << ", ";
    }
    std::cout << std::endl << std::endl;
    
    // Read IP lists for selected providers only
    std::map<std::string, std::vector<std::string>> provider_ips;
    
    for (const std::string& provider : selected_providers) {
        std::string filename = "../../ip_lists/" + provider + ".txt";
        std::vector<std::string> ips = readIPList(filename);
        
        if (!ips.empty()) {
            provider_ips[provider] = ips;
            std::cout << "Loaded " << ips.size() << " IPs for " << provider << std::endl;
        } else {
            std::cout << "Warning: No IPs found for " << provider << " (file: " << filename << ")" << std::endl;
        }
    }
    
    if (provider_ips.empty()) {
        std::cerr << "Error: No IP lists found. Please add IP files to ip_lists/ directory." << std::endl;
        return 1;
    }
    
    // Create tester with high concurrency
    BestCDNTester tester(200, 3.0);  // 200 concurrent requests, 3s timeout
    
    // Run tests
    tester.testAllIPs(provider_ips);
    
    // Save results
    auto now = std::chrono::system_clock::now();
    auto time_t = std::chrono::system_clock::to_time_t(now);
    auto tm = *std::localtime(&time_t);
    
    char timestamp[20];
    std::strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &tm);
    
    std::string results_file = "../../results/cpp_results_" + std::string(timestamp) + ".json";
    std::string accessible_file = "../../results/cpp_accessible_" + std::string(timestamp) + ".txt";
    
    tester.saveResults(results_file);
    tester.saveAccessibleIPs(accessible_file);
    
    return 0;
}