#include "ServerManager.hpp"
#include <fstream>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <regex>
#include <cpr/cpr.h>

using json = nlohmann::json;

// ServerConfig implementation
json ServerConfig::toJson() const {
    return json{
        {"url", url},
        {"name", name},
        {"default", isDefault},
        {"writeToken", writeToken},
        {"added", addedDate}
    };
}

ServerConfig ServerConfig::fromJson(const json& j) {
    ServerConfig config;
    config.url = j.value("url", "");
    config.name = j.value("name", "");
    config.isDefault = j.value("default", false);
    config.writeToken = j.value("writeToken", "");
    config.addedDate = j.value("added", "");
    return config;
}

// ServerManager implementation
ServerManager::ServerManager() {
    const char* home = getenv("HOME");
    if (home) {
        configPath_ = std::filesystem::path(home) / ".config" / "getpkg" / "servers.json";
    }
}

bool ServerManager::addServer(const std::string& serverUrl, const std::string& writeToken) {
    if (!validateServerUrl(serverUrl)) {
        std::cerr << "Invalid server URL: " << serverUrl << std::endl;
        return false;
    }
    
    // Check if server already exists
    if (findServer(serverUrl) != nullptr) {
        std::cerr << "Server already exists: " << serverUrl << std::endl;
        return false;
    }
    
    // Check if server is reachable
    if (!isServerReachable(serverUrl)) {
        std::cerr << "Warning: Server may not be reachable: " << serverUrl << std::endl;
        // Continue anyway - server might be temporarily down
    }
    
    ServerConfig config;
    config.url = serverUrl;
    config.name = serverUrl; // Use URL as default name
    config.isDefault = servers_.empty(); // First server becomes default
    config.writeToken = writeToken;
    config.addedDate = getCurrentTimestamp();
    
    servers_.push_back(config);
    
    return saveConfiguration();
}

bool ServerManager::removeServer(const std::string& serverUrl) {
    auto it = std::find_if(servers_.begin(), servers_.end(),
                          [&serverUrl](const ServerConfig& config) {
                              return config.url == serverUrl;
                          });
    
    if (it == servers_.end()) {
        std::cerr << "Server not found: " << serverUrl << std::endl;
        return false;
    }
    
    // Don't allow removing the last server
    if (servers_.size() == 1) {
        std::cerr << "Cannot remove the last server. Add another server first." << std::endl;
        return false;
    }
    
    bool wasDefault = it->isDefault;
    servers_.erase(it);
    
    // If we removed the default server, make the first remaining server default
    if (wasDefault && !servers_.empty()) {
        servers_[0].isDefault = true;
    }
    
    return saveConfiguration();
}

std::vector<std::string> ServerManager::getServers() const {
    std::vector<std::string> urls;
    for (const auto& server : servers_) {
        urls.push_back(server.url);
    }
    return urls;
}

std::string ServerManager::getDefaultServer() const {
    for (const auto& server : servers_) {
        if (server.isDefault) {
            return server.url;
        }
    }
    
    // If no default is set, return the first server
    if (!servers_.empty()) {
        return servers_[0].url;
    }
    
    return "getpkg.xyz"; // Fallback to original default
}

std::string ServerManager::getDefaultPublishServer() const {
    // Return first server with a write token
    for (const auto& server : servers_) {
        if (!server.writeToken.empty()) {
            return server.url;
        }
    }
    
    // If no server has a token, return the default server
    return getDefaultServer();
}

bool ServerManager::setWriteToken(const std::string& serverUrl, const std::string& token) {
    ServerConfig* server = findServer(serverUrl);
    if (server == nullptr) {
        std::cerr << "Server not found: " << serverUrl << std::endl;
        return false;
    }
    
    server->writeToken = token;
    return saveConfiguration();
}

std::string ServerManager::getWriteToken(const std::string& serverUrl) const {
    const ServerConfig* server = findServer(serverUrl);
    if (server != nullptr) {
        return server->writeToken;
    }
    return "";
}

bool ServerManager::hasWriteToken(const std::string& serverUrl) const {
    const ServerConfig* server = findServer(serverUrl);
    return server != nullptr && !server->writeToken.empty();
}

std::vector<std::string> ServerManager::getServersWithTokens() const {
    std::vector<std::string> serversWithTokens;
    for (const auto& server : servers_) {
        if (!server.writeToken.empty()) {
            serversWithTokens.push_back(server.url);
        }
    }
    return serversWithTokens;
}

bool ServerManager::loadConfiguration() {
    if (!std::filesystem::exists(configPath_)) {
        ensureDefaultConfiguration();
        return true;
    }
    
    try {
        std::ifstream file(configPath_);
        if (!file.is_open()) {
            std::cerr << "Failed to open server configuration file: " << configPath_ << std::endl;
            ensureDefaultConfiguration();
            return true;
        }
        
        json config;
        file >> config;
        
        if (!config.contains("servers") || !config["servers"].is_array()) {
            std::cerr << "Invalid server configuration format" << std::endl;
            ensureDefaultConfiguration();
            return true;
        }
        
        servers_.clear();
        for (const auto& serverJson : config["servers"]) {
            try {
                servers_.push_back(ServerConfig::fromJson(serverJson));
            } catch (const std::exception& e) {
                std::cerr << "Warning: Skipping invalid server config: " << e.what() << std::endl;
            }
        }
        
        // Ensure we have at least one server
        if (servers_.empty()) {
            ensureDefaultConfiguration();
        }
        
        return true;
    } catch (const std::exception& e) {
        std::cerr << "Error loading server configuration: " << e.what() << std::endl;
        ensureDefaultConfiguration();
        return true;
    }
}

bool ServerManager::saveConfiguration() {
    try {
        // Ensure directory exists
        std::filesystem::create_directories(configPath_.parent_path());
        
        json config;
        config["version"] = "1.0";
        config["lastUpdated"] = getCurrentTimestamp();
        
        json serversArray = json::array();
        for (const auto& server : servers_) {
            serversArray.push_back(server.toJson());
        }
        config["servers"] = serversArray;
        
        std::ofstream file(configPath_);
        if (!file.is_open()) {
            std::cerr << "Failed to open server configuration file for writing: " << configPath_ << std::endl;
            return false;
        }
        
        file << config.dump(2);
        return file.good();
    } catch (const std::exception& e) {
        std::cerr << "Error saving server configuration: " << e.what() << std::endl;
        return false;
    }
}

void ServerManager::ensureDefaultConfiguration() {
    servers_.clear();
    
    ServerConfig defaultServer;
    defaultServer.url = "getpkg.xyz";
    defaultServer.name = "Official getpkg Registry";
    defaultServer.isDefault = true;
    defaultServer.writeToken = "";
    defaultServer.addedDate = getCurrentTimestamp();
    
    servers_.push_back(defaultServer);
    
    saveConfiguration();
}

bool ServerManager::migrateFromLegacy() {
    const char* home = getenv("HOME");
    if (!home) {
        return false;
    }
    
    std::filesystem::path legacyTokenPath = std::filesystem::path(home) / ".config" / "getpkg.xyz" / "write_token.txt";
    
    if (std::filesystem::exists(legacyTokenPath)) {
        try {
            std::ifstream tokenFile(legacyTokenPath);
            std::string token;
            std::getline(tokenFile, token);
            
            if (!token.empty()) {
                // Set the token for getpkg.xyz server
                setWriteToken("getpkg.xyz", token);
                
                // Optionally remove the legacy token file
                // std::filesystem::remove(legacyTokenPath);
                
                std::cout << "Migrated legacy write token for getpkg.xyz" << std::endl;
                return true;
            }
        } catch (const std::exception& e) {
            std::cerr << "Warning: Failed to migrate legacy token: " << e.what() << std::endl;
        }
    }
    
    return false;
}

bool ServerManager::validateServerUrl(const std::string& url) const {
    if (url.empty() || url.length() > 253) { // DNS name length limit
        return false;
    }
    
    // Basic URL validation - should be a valid hostname or IP
    // Allow formats like: example.com, sub.example.com, 192.168.1.1, localhost
    std::regex urlPattern(R"(^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$)");
    
    if (!std::regex_match(url, urlPattern)) {
        return false;
    }
    
    // Additional checks
    if (url.find("..") != std::string::npos) {
        return false;
    }
    
    if (url.front() == '.' || url.back() == '.') {
        return false;
    }
    
    return true;
}

bool ServerManager::isServerReachable(const std::string& url) const {
    try {
        std::string testUrl = "https://" + url + "/";
        
        auto response = cpr::Head(cpr::Url{testUrl},
                                 cpr::Timeout{5000}, // 5 seconds
                                 cpr::VerifySsl{true});
        
        // Accept any response that indicates the server is reachable
        // (200, 404, 403, etc. - as long as we get a response)
        return response.status_code > 0;
    } catch (const std::exception& e) {
        return false;
    }
}

ServerConfig* ServerManager::findServer(const std::string& url) {
    auto it = std::find_if(servers_.begin(), servers_.end(),
                          [&url](const ServerConfig& config) {
                              return config.url == url;
                          });
    return (it != servers_.end()) ? &(*it) : nullptr;
}

const ServerConfig* ServerManager::findServer(const std::string& url) const {
    auto it = std::find_if(servers_.begin(), servers_.end(),
                          [&url](const ServerConfig& config) {
                              return config.url == url;
                          });
    return (it != servers_.end()) ? &(*it) : nullptr;
}

std::string ServerManager::getCurrentTimestamp() const {
    auto now = std::chrono::system_clock::now();
    auto time_t = std::chrono::system_clock::to_time_t(now);
    
    std::stringstream ss;
    ss << std::put_time(std::gmtime(&time_t), "%Y-%m-%dT%H:%M:%SZ");
    return ss.str();
}