#include "GetbinClient.hpp"
#include <drogon/HttpClient.h>
#include <trantor/net/EventLoop.h>
#include <openssl/ssl.h>
#include <openssl/opensslconf.h>
#include <fstream>
#include <sstream>
#include <nlohmann/json.hpp>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
#include <cstdio>
#include <map>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <ctime>
#include <algorithm>
#include <set>

using json = nlohmann::json;

static constexpr const char* SERVER_HOST = "getpkg.xyz";

// Initialize SSL to use only secure protocols
static class SSLInitializer {
public:
    SSLInitializer() {
        // Disable SSL 2.0, 3.0, TLS 1.0, and TLS 1.1
        SSL_load_error_strings();
        SSL_library_init();
        // Note: This doesn't completely silence the warning but ensures we're using secure protocols
    }
} ssl_init;

static std::string find_ca_certificates() {
    // Common CA certificate locations across different Linux distributions
    const std::vector<std::string> ca_paths = {
        "/etc/ssl/certs/ca-certificates.crt",     // Debian/Ubuntu/Raspbian
        "/etc/pki/tls/certs/ca-bundle.crt",       // Fedora/RHEL/CentOS
        "/etc/ssl/ca-bundle.pem",                  // OpenSUSE
        "/etc/pki/tls/cert.pem",                   // Fedora/RHEL alternative
        "/etc/ssl/certs/ca-bundle.crt",            // Some distros
        "/etc/ssl/cert.pem",                       // Alpine Linux
        "/usr/local/share/certs/ca-root-nss.crt",  // FreeBSD
        "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7+
        "/etc/ca-certificates/extracted/tls-ca-bundle.pem"   // Arch Linux
    };
    
    for (const auto& path : ca_paths) {
        std::ifstream file(path);
        if (file.good()) {
            file.close();
            return path;
        }
    }
    
    return "";
}

GetbinClient::GetbinClient() {}

bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) {
    bool success = false;
    bool done = false;
    std::mutex mtx;
    std::condition_variable cv;
    
    std::thread worker([&]() {
        trantor::EventLoop loop;
        
        auto client = drogon::HttpClient::newHttpClient(
            "https://" + std::string(SERVER_HOST), 
            &loop,
            false,  // useOldTLS = false (disable TLS 1.0/1.1)
            true    // validateCert = true
        );
        
        // Configure SSL certificates for HTTPS
        std::string ca_path = find_ca_certificates();
        if (!ca_path.empty()) {
            // Use addSSLConfigs with proper parameter names for OpenSSL
            std::vector<std::pair<std::string, std::string>> sslConfigs;
            sslConfigs.push_back({"VerifyCAFile", ca_path});
            client->addSSLConfigs(sslConfigs);
        } else {
            // If no CA certificates found, print warning but continue
            std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
        }
        
        client->enableCookies();
        client->setUserAgent("getpkg/1.0");
        
        std::string object_path = "/object/" + toolName + ":" + arch;
        
        auto req = drogon::HttpRequest::newHttpRequest();
        req->setMethod(drogon::Get);
        req->setPath(object_path);
        
        client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
            std::lock_guard<std::mutex> lock(mtx);
            if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
                std::ofstream ofs(outPath, std::ios::binary);
                if (ofs) {
                    const auto& body = response->getBody();
                    ofs.write(body.data(), body.size());
                    success = ofs.good();
                }
            } else {
                std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl;
            }
            done = true;
            cv.notify_one();
            loop.quit();
        }, 30.0); // 30 second timeout
        
        loop.loop();
    });
    
    // Wait for completion
    {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&] { return done; });
    }
    
    worker.join();
    return success;
}

bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token) {
    // Read file first
    std::ifstream ifs(archivePath, std::ios::binary);
    if (!ifs) {
        std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl;
        return false;
    }
    std::string file_content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
    
    // Compose metadata
    json metadata = { {"labeltags", json::array()} };
    std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1);
    size_t dot = filename.find('.');
    std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename;
    metadata["labeltags"].push_back(labeltag);
    
    bool success = false;
    bool done = false;
    std::mutex mtx;
    std::condition_variable cv;
    
    std::thread worker([&]() {
        trantor::EventLoop loop;
        
        auto client = drogon::HttpClient::newHttpClient(
            "https://" + std::string(SERVER_HOST), 
            &loop,
            false,  // useOldTLS = false (disable TLS 1.0/1.1)
            true    // validateCert = true
        );
        
        // Configure SSL certificates
        std::string ca_path = find_ca_certificates();
        std::vector<std::pair<std::string, std::string>> sslConfigs;
        if (!ca_path.empty()) {
            sslConfigs.push_back({"VerifyCAFile", ca_path});
        }
        // Configure SSL for secure connections
        client->addSSLConfigs(sslConfigs);
        
        if (ca_path.empty()) {
            std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
        }
        
        client->enableCookies();
        client->setUserAgent("getpkg/1.0");
        
        // Create upload file from memory content
        // First save content to a temporary file since UploadFile expects a file path
        std::string temp_file = "/tmp/getpkg_upload_" + std::to_string(std::time(nullptr)) + ".tgz";
        std::ofstream temp_ofs(temp_file, std::ios::binary);
        if (!temp_ofs) {
            std::cerr << "[GetbinClient::upload] Failed to create temporary file: " << temp_file << std::endl;
            success = false;
            done = true;
            cv.notify_one();
            loop.quit();
            return;
        }
        temp_ofs.write(file_content.data(), file_content.size());
        temp_ofs.close();
        
        // Create upload request with file
        drogon::UploadFile upload_file(temp_file);
        
        auto req = drogon::HttpRequest::newFileUploadRequest({upload_file});
        req->setMethod(drogon::Put);
        req->setPath("/upload");
        req->addHeader("Authorization", "Bearer " + token);
        
        // Add metadata as form parameter
        req->setParameter("metadata", metadata.dump());
        
        client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
            std::lock_guard<std::mutex> lock(mtx);
            if (result == drogon::ReqResult::Ok && response) {
                int status_code = static_cast<int>(response->getStatusCode());
                std::string response_body(response->getBody());
                
                if (status_code == 200 || status_code == 201) {
                    try {
                        auto resp_json = json::parse(response_body);
                        if (resp_json.contains("url")) outUrl = resp_json["url"].get<std::string>();
                        if (resp_json.contains("hash")) outHash = resp_json["hash"].get<std::string>();
                        success = true;
                    } catch (const std::exception& e) {
                        std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl;
                        std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
                    }
                } else {
                    std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl;
                    std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
                }
            } else {
                std::cerr << "[GetbinClient::upload] HTTP /upload request failed." << std::endl;
            }
            done = true;
            cv.notify_one();
            loop.quit();
        }, 60.0); // 60 second timeout
        
        loop.loop();
        
        // Clean up temporary file
        std::remove(temp_file.c_str());
    });
    
    // Wait for completion
    {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&] { return done; });
    }
    
    worker.join();
    return success;
}

bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) {
    bool success = false;
    bool done = false;
    std::mutex mtx;
    std::condition_variable cv;
    
    std::thread worker([&]() {
        trantor::EventLoop loop;
        
        auto client = drogon::HttpClient::newHttpClient(
            "https://" + std::string(SERVER_HOST), 
            &loop,
            false,  // useOldTLS = false (disable TLS 1.0/1.1)
            true    // validateCert = true
        );
        
        // Configure SSL certificates
        std::string ca_path = find_ca_certificates();
        std::vector<std::pair<std::string, std::string>> sslConfigs;
        if (!ca_path.empty()) {
            sslConfigs.push_back({"VerifyCAFile", ca_path});
        }
        // Configure SSL for secure connections
        client->addSSLConfigs(sslConfigs);
        
        if (ca_path.empty()) {
            std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
        }
        
        client->enableCookies();
        client->setUserAgent("getpkg/1.0");
        
        std::string hash_path = "/hash/" + toolName + ":" + arch;
        
        auto req = drogon::HttpRequest::newHttpRequest();
        req->setMethod(drogon::Get);
        req->setPath(hash_path);
        
        client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
            std::lock_guard<std::mutex> lock(mtx);
            if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
                std::string response_body(response->getBody());
                
                // Try to parse hash from response body
                try {
                    // Try JSON first
                    auto resp_json = json::parse(response_body);
                    if (resp_json.contains("hash")) {
                        outHash = resp_json["hash"].get<std::string>();
                        success = true;
                    }
                } catch (...) {
                    // Not JSON, treat as plain text
                    outHash = response_body;
                    // Remove trailing newline if present
                    if (!outHash.empty() && outHash.back() == '\n') {
                        outHash.pop_back();
                    }
                    success = !outHash.empty();
                }
            }
            done = true;
            cv.notify_one();
            loop.quit();
        }, 10.0); // 10 second timeout
        
        loop.loop();
    });
    
    // Wait for completion
    {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&] { return done; });
    }
    
    worker.join();
    return success;
}

bool GetbinClient::deleteObject(const std::string& hash, const std::string& token) {
    bool success = false;
    bool done = false;
    std::mutex mtx;
    std::condition_variable cv;
    
    std::thread worker([&]() {
        trantor::EventLoop loop;
        
        auto client = drogon::HttpClient::newHttpClient(
            "https://" + std::string(SERVER_HOST), 
            &loop,
            false,  // useOldTLS = false (disable TLS 1.0/1.1)
            true    // validateCert = true
        );
        
        // Configure SSL certificates
        std::string ca_path = find_ca_certificates();
        std::vector<std::pair<std::string, std::string>> sslConfigs;
        if (!ca_path.empty()) {
            sslConfigs.push_back({"VerifyCAFile", ca_path});
        }
        // Configure SSL for secure connections
        client->addSSLConfigs(sslConfigs);
        
        if (ca_path.empty()) {
            std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
        }
        
        client->enableCookies();
        client->setUserAgent("getpkg/1.0");
        
        std::string delete_path = "/deleteobject?hash=" + hash;
        
        auto req = drogon::HttpRequest::newHttpRequest();
        req->setMethod(drogon::Get);
        req->setPath(delete_path);
        req->addHeader("Authorization", "Bearer " + token);
        
        client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
            std::lock_guard<std::mutex> lock(mtx);
            if (result == drogon::ReqResult::Ok && response) {
                int status_code = static_cast<int>(response->getStatusCode());
                std::string response_body(response->getBody());
                
                if (status_code == 200) {
                    // Check if the response indicates success
                    try {
                        auto resp_json = json::parse(response_body);
                        if (resp_json.contains("result") && resp_json["result"] == "success") {
                            success = true;
                        }
                    } catch (...) {
                        // If not JSON, assume success if 200 OK
                        success = true;
                    }
                } else {
                    std::cerr << "[GetbinClient::deleteObject] HTTP error: status code " << status_code << std::endl;
                    std::cerr << "[GetbinClient::deleteObject] Response body: " << response_body << std::endl;
                }
            } else {
                std::cerr << "[GetbinClient::deleteObject] HTTP request failed." << std::endl;
            }
            done = true;
            cv.notify_one();
            loop.quit();
        }, 10.0); // 10 second timeout
        
        loop.loop();
    });
    
    // Wait for completion
    {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&] { return done; });
    }
    
    worker.join();
    return success;
}
bool GetbinClient::listPackages(std::vector<std::string>& outPackages) {
    outPackages.clear();
    
    // Set up SSL configuration
    std::string ca_path = find_ca_certificates();
    
    bool success = false;
    bool done = false;
    std::mutex mtx;
    std::condition_variable cv;
    
    std::thread worker([&]() {
        trantor::EventLoop loop;
        
        auto client = drogon::HttpClient::newHttpClient(
            "https://" + std::string(SERVER_HOST), 
            &loop,
            false,  // useOldTLS = false (disable TLS 1.0/1.1)
            true    // validateCert = true
        );
        std::vector<std::pair<std::string, std::string>> sslConfigs;
        if (!ca_path.empty()) {
            sslConfigs.push_back({"VerifyCAFile", ca_path});
        }
        // Configure SSL for secure connections
        client->addSSLConfigs(sslConfigs);
        
        auto req = drogon::HttpRequest::newHttpRequest();
        req->setMethod(drogon::Get);
        req->setPath("/dir");
        
        client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
            if (result == drogon::ReqResult::Ok) {
                int status_code = response->getStatusCode();
                std::string response_body = std::string(response->getBody());
                
                if (status_code == 200) {
                    try {
                        json json_response = json::parse(response_body);
                        
                        if (json_response.contains("entries") && json_response["entries"].is_array()) {
                            for (const auto& entry : json_response["entries"]) {
                                if (entry.contains("labeltags") && entry["labeltags"].is_array()) {
                                    for (const auto& labeltag : entry["labeltags"]) {
                                        if (labeltag.is_string()) {
                                            std::string name = labeltag.get<std::string>();
                                            // Extract tool name (remove architecture suffix if present)
                                            size_t colon_pos = name.find(":");
                                            if (colon_pos != std::string::npos) {
                                                name = name.substr(0, colon_pos);
                                            }
                                            
                                            // Skip empty names
                                            if (name.empty()) continue;
                                            
                                            // Add to list if not already present
                                            if (std::find(outPackages.begin(), outPackages.end(), name) == outPackages.end()) {
                                                outPackages.push_back(name);
                                            }
                                        }
                                    }
                                }
                            }
                            success = true;
                        }
                    } catch (const std::exception& e) {
                        std::cerr << "[GetbinClient::listPackages] JSON parse error: " << e.what() << std::endl;
                    }
                } else {
                    std::cerr << "[GetbinClient::listPackages] HTTP error: status code " << status_code << std::endl;
                }
            } else {
                std::cerr << "[GetbinClient::listPackages] HTTP request failed." << std::endl;
            }
            done = true;
            cv.notify_one();
            loop.quit();
        }, 10.0);
        
        loop.loop();
    });
    
    // Wait for completion
    {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [&] { return done; });
    }
    
    worker.join();
    
    // Filter out duplicates where we have both toolname and toolname-noarch
    // Keep the base name and remove the -noarch variant
    std::vector<std::string> filteredPackages;
    std::set<std::string> baseNames;
    
    // First pass: collect all base names (without -noarch)
    for (const auto& pkg : outPackages) {
        const std::string suffix = "-noarch";
        if (pkg.length() < suffix.length() || pkg.substr(pkg.length() - suffix.length()) != suffix) {
            baseNames.insert(pkg);
        }
    }
    
    // Second pass: add packages, skipping -noarch variants if base exists
    for (const auto& pkg : outPackages) {
        const std::string suffix = "-noarch";
        if (pkg.length() >= suffix.length() && pkg.substr(pkg.length() - suffix.length()) == suffix) {
            std::string baseName = pkg.substr(0, pkg.length() - suffix.length());
            if (baseNames.find(baseName) == baseNames.end()) {
                filteredPackages.push_back(pkg); // Keep -noarch only if no base version
            }
        } else {
            filteredPackages.push_back(pkg); // Always keep base versions
        }
    }
    
    outPackages = std::move(filteredPackages);
    
    // Sort the packages for better display
    std::sort(outPackages.begin(), outPackages.end());
    
    return success;
}
