#include "PackageMetadata.hpp"
#include <fstream>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <regex>
#include <cstdlib>

// PackageMetadata implementation

PackageMetadata::PackageMetadata(const std::string& name, const std::string& version, 
                               const std::string& hash, const std::string& arch,
                               const std::string& sourceServer, const std::string& installDate)
    : name(name), version(version), hash(hash), arch(arch), sourceServer(sourceServer) {
    
    if (installDate.empty()) {
        this->installDate = getCurrentTimestamp();
    } else {
        this->installDate = installDate;
    }
}

json PackageMetadata::toJson() const {
    json j;
    j["name"] = name;
    j["version"] = version;
    j["hash"] = hash;
    j["arch"] = arch;
    j["sourceServer"] = sourceServer;
    j["installDate"] = installDate;
    j["lastUpdated"] = getCurrentTimestamp();
    return j;
}

PackageMetadata PackageMetadata::fromJson(const json& j) {
    PackageMetadata metadata;
    
    // Required fields
    if (j.contains("name") && j["name"].is_string()) {
        metadata.name = j["name"].get<std::string>();
    }
    if (j.contains("version") && j["version"].is_string()) {
        metadata.version = j["version"].get<std::string>();
    }
    if (j.contains("hash") && j["hash"].is_string()) {
        metadata.hash = j["hash"].get<std::string>();
    }
    if (j.contains("arch") && j["arch"].is_string()) {
        metadata.arch = j["arch"].get<std::string>();
    }
    
    // New fields with defaults
    if (j.contains("sourceServer") && j["sourceServer"].is_string()) {
        metadata.sourceServer = j["sourceServer"].get<std::string>();
    } else {
        metadata.sourceServer = "getpkg.xyz";  // Default fallback
    }
    
    if (j.contains("installDate") && j["installDate"].is_string()) {
        metadata.installDate = j["installDate"].get<std::string>();
    } else {
        metadata.installDate = metadata.getCurrentTimestamp();
    }
    
    return metadata;
}

PackageMetadata PackageMetadata::fromLegacyJson(const json& j, const std::string& defaultServer) {
    PackageMetadata metadata;
    
    // Legacy format only has: name, version, hash, arch
    if (j.contains("name") && j["name"].is_string()) {
        metadata.name = j["name"].get<std::string>();
    }
    if (j.contains("version") && j["version"].is_string()) {
        metadata.version = j["version"].get<std::string>();
    }
    if (j.contains("hash") && j["hash"].is_string()) {
        metadata.hash = j["hash"].get<std::string>();
    }
    if (j.contains("arch") && j["arch"].is_string()) {
        metadata.arch = j["arch"].get<std::string>();
    }
    
    // Set defaults for new fields
    metadata.sourceServer = defaultServer;
    metadata.installDate = metadata.getCurrentTimestamp();
    
    return metadata;
}

bool PackageMetadata::isValid() const {
    return isValidName() && isValidVersion() && isValidHash() && 
           isValidArch() && isValidServerUrl() && isValidTimestamp();
}

std::string PackageMetadata::getValidationError() const {
    if (!isValidName()) {
        return "Invalid package name: must be non-empty and contain only alphanumeric characters, hyphens, and underscores";
    }
    if (!isValidVersion()) {
        return "Invalid version: must be non-empty";
    }
    if (!isValidHash()) {
        return "Invalid hash: must be non-empty and contain only hexadecimal characters";
    }
    if (!isValidArch()) {
        return "Invalid architecture: must be non-empty";
    }
    if (!isValidServerUrl()) {
        return "Invalid source server: must be non-empty and contain valid characters";
    }
    if (!isValidTimestamp()) {
        return "Invalid install date: must be non-empty";
    }
    return "";
}

bool PackageMetadata::saveToFile(const std::filesystem::path& filePath) const {
    if (!isValid()) {
        std::cerr << "Cannot save invalid package metadata: " << getValidationError() << std::endl;
        return false;
    }
    
    try {
        // Ensure parent directory exists
        std::filesystem::create_directories(filePath.parent_path());
        
        std::ofstream file(filePath);
        if (!file.is_open()) {
            std::cerr << "Failed to open file for writing: " << filePath << std::endl;
            return false;
        }
        
        file << toJson().dump(2);
        file.close();
        return true;
    } catch (const std::exception& e) {
        std::cerr << "Error saving package metadata to " << filePath << ": " << e.what() << std::endl;
        return false;
    }
}

PackageMetadata PackageMetadata::loadFromFile(const std::filesystem::path& filePath) {
    PackageMetadata metadata;
    
    try {
        if (!std::filesystem::exists(filePath)) {
            std::cerr << "Package metadata file does not exist: " << filePath << std::endl;
            return metadata;
        }
        
        std::ifstream file(filePath);
        if (!file.is_open()) {
            std::cerr << "Failed to open file for reading: " << filePath << std::endl;
            return metadata;
        }
        
        json j;
        file >> j;
        file.close();
        
        metadata = fromJson(j);
        
        if (!metadata.isValid()) {
            std::cerr << "Loaded package metadata is invalid: " << metadata.getValidationError() << std::endl;
        }
        
    } catch (const std::exception& e) {
        std::cerr << "Error loading package metadata from " << filePath << ": " << e.what() << std::endl;
    }
    
    return metadata;
}

std::string PackageMetadata::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();
}

bool PackageMetadata::needsUpdate(const std::string& remoteHash) const {
    return hash != remoteHash;
}

// Private validation methods
bool PackageMetadata::isValidName() const {
    if (name.empty()) return false;
    
    // Package name should contain only alphanumeric characters, hyphens, and underscores
    std::regex namePattern("^[a-zA-Z0-9_-]+$");
    return std::regex_match(name, namePattern);
}

bool PackageMetadata::isValidVersion() const {
    return !version.empty();
}

bool PackageMetadata::isValidHash() const {
    if (hash.empty()) return false;
    
    // Hash should contain only hexadecimal characters
    std::regex hashPattern("^[a-fA-F0-9]+$");
    return std::regex_match(hash, hashPattern);
}

bool PackageMetadata::isValidArch() const {
    return !arch.empty();
}

bool PackageMetadata::isValidServerUrl() const {
    if (sourceServer.empty()) return false;
    
    // Basic server URL validation - should not contain invalid characters
    std::regex serverPattern("^[a-zA-Z0-9._-]+$");
    return std::regex_match(sourceServer, serverPattern);
}

bool PackageMetadata::isValidTimestamp() const {
    return !installDate.empty();
}

// PackageMetadataManager implementation

PackageMetadataManager::PackageMetadataManager() {
    const char* home = std::getenv("HOME");
    if (home) {
        configDir_ = std::filesystem::path(home) / ".config" / "getpkg";
        packagesDir_ = configDir_ / "packages";
    }
}

PackageMetadataManager::PackageMetadataManager(const std::filesystem::path& configDir) 
    : configDir_(configDir), packagesDir_(configDir / "packages") {
}

bool PackageMetadataManager::ensurePackagesDirectory() {
    try {
        if (!std::filesystem::exists(packagesDir_)) {
            std::filesystem::create_directories(packagesDir_);
        }
        return std::filesystem::is_directory(packagesDir_);
    } catch (const std::exception& e) {
        std::cerr << "Error creating packages directory: " << e.what() << std::endl;
        return false;
    }
}

std::filesystem::path PackageMetadataManager::getPackagesDirectory() const {
    return packagesDir_;
}

std::filesystem::path PackageMetadataManager::getPackageFilePath(const std::string& toolName) const {
    return packagesDir_ / (toolName + ".json");
}

bool PackageMetadataManager::savePackageMetadata(const PackageMetadata& metadata) {
    if (!ensurePackagesDirectory()) {
        return false;
    }
    
    std::filesystem::path filePath = getPackageFilePath(metadata.name);
    return metadata.saveToFile(filePath);
}

PackageMetadata PackageMetadataManager::loadPackageMetadata(const std::string& toolName) {
    std::filesystem::path filePath = getPackageFilePath(toolName);
    return PackageMetadata::loadFromFile(filePath);
}

bool PackageMetadataManager::packageExists(const std::string& toolName) const {
    std::filesystem::path filePath = getPackageFilePath(toolName);
    return std::filesystem::exists(filePath);
}

bool PackageMetadataManager::removePackageMetadata(const std::string& toolName) {
    try {
        std::filesystem::path filePath = getPackageFilePath(toolName);
        if (std::filesystem::exists(filePath)) {
            return std::filesystem::remove(filePath);
        }
        return true; // Already doesn't exist
    } catch (const std::exception& e) {
        std::cerr << "Error removing package metadata for " << toolName << ": " << e.what() << std::endl;
        return false;
    }
}

bool PackageMetadataManager::migrateFromLegacyFormat() {
    try {
        std::vector<std::string> legacyFiles = findLegacyPackageFiles();
        
        if (legacyFiles.empty()) {
            return true; // Nothing to migrate
        }
        
        if (!ensurePackagesDirectory()) {
            std::cerr << "Failed to create packages directory for migration" << std::endl;
            return false;
        }
        
        int successCount = 0;
        for (const std::string& fileName : legacyFiles) {
            std::filesystem::path legacyPath = configDir_ / fileName;
            if (migrateLegacyPackageFile(legacyPath)) {
                successCount++;
            }
        }
        
        std::cout << "Migrated " << successCount << " of " << legacyFiles.size() << " legacy package files" << std::endl;
        return successCount == legacyFiles.size();
        
    } catch (const std::exception& e) {
        std::cerr << "Error during migration: " << e.what() << std::endl;
        return false;
    }
}

std::vector<std::string> PackageMetadataManager::findLegacyPackageFiles() const {
    std::vector<std::string> legacyFiles;
    
    try {
        if (!std::filesystem::exists(configDir_)) {
            return legacyFiles;
        }
        
        for (const auto& entry : std::filesystem::directory_iterator(configDir_)) {
            if (entry.is_regular_file() && entry.path().extension() == ".json") {
                std::string fileName = entry.path().filename().string();
                
                // Skip if it's already in the packages directory or is servers.json
                if (fileName != "servers.json") {
                    legacyFiles.push_back(fileName);
                }
            }
        }
    } catch (const std::exception& e) {
        std::cerr << "Error finding legacy package files: " << e.what() << std::endl;
    }
    
    return legacyFiles;
}

bool PackageMetadataManager::migrateLegacyPackageFile(const std::filesystem::path& legacyPath, const std::string& defaultServer) {
    try {
        if (!std::filesystem::exists(legacyPath)) {
            return false;
        }
        
        // Load legacy format
        std::ifstream file(legacyPath);
        if (!file.is_open()) {
            std::cerr << "Failed to open legacy file: " << legacyPath << std::endl;
            return false;
        }
        
        json legacyJson;
        file >> legacyJson;
        file.close();
        
        // Convert to new format
        PackageMetadata metadata = PackageMetadata::fromLegacyJson(legacyJson, defaultServer);
        
        if (!metadata.isValid()) {
            std::cerr << "Invalid metadata after migration from " << legacyPath << ": " << metadata.getValidationError() << std::endl;
            return false;
        }
        
        // Save in new location
        if (!savePackageMetadata(metadata)) {
            std::cerr << "Failed to save migrated metadata for " << metadata.name << std::endl;
            return false;
        }
        
        // Remove legacy file
        std::filesystem::remove(legacyPath);
        
        std::cout << "Migrated package metadata: " << metadata.name << " from " << defaultServer << std::endl;
        return true;
        
    } catch (const std::exception& e) {
        std::cerr << "Error migrating legacy file " << legacyPath << ": " << e.what() << std::endl;
        return false;
    }
}

std::vector<std::string> PackageMetadataManager::listInstalledPackages() const {
    std::vector<std::string> packages;
    
    try {
        if (!std::filesystem::exists(packagesDir_)) {
            return packages;
        }
        
        for (const auto& entry : std::filesystem::directory_iterator(packagesDir_)) {
            if (entry.is_regular_file() && entry.path().extension() == ".json") {
                std::string toolName = entry.path().stem().string();
                packages.push_back(toolName);
            }
        }
    } catch (const std::exception& e) {
        std::cerr << "Error listing installed packages: " << e.what() << std::endl;
    }
    
    return packages;
}

std::vector<PackageMetadata> PackageMetadataManager::getAllPackageMetadata() const {
    std::vector<PackageMetadata> allMetadata;
    
    std::vector<std::string> packages = listInstalledPackages();
    for (const std::string& packageName : packages) {
        PackageMetadata metadata = const_cast<PackageMetadataManager*>(this)->loadPackageMetadata(packageName);
        if (metadata.isValid()) {
            allMetadata.push_back(metadata);
        }
    }
    
    return allMetadata;
}

bool PackageMetadataManager::validateAllPackageMetadata() const {
    std::vector<std::string> packages = listInstalledPackages();
    
    for (const std::string& packageName : packages) {
        PackageMetadata metadata = const_cast<PackageMetadataManager*>(this)->loadPackageMetadata(packageName);
        if (!metadata.isValid()) {
            std::cerr << "Invalid metadata for package " << packageName << ": " << metadata.getValidationError() << std::endl;
            return false;
        }
    }
    
    return true;
}

int PackageMetadataManager::cleanupInvalidMetadata() {
    int removedCount = 0;
    std::vector<std::string> packages = listInstalledPackages();
    
    for (const std::string& packageName : packages) {
        PackageMetadata metadata = loadPackageMetadata(packageName);
        if (!metadata.isValid()) {
            std::cerr << "Removing invalid metadata for package " << packageName << ": " << metadata.getValidationError() << std::endl;
            if (removePackageMetadata(packageName)) {
                removedCount++;
            }
        }
    }
    
    return removedCount;
}

bool PackageMetadataManager::isValidPackageFile(const std::filesystem::path& filePath) const {
    return filePath.extension() == ".json" && std::filesystem::is_regular_file(filePath);
}

std::string PackageMetadataManager::extractToolNameFromPath(const std::filesystem::path& filePath) const {
    return filePath.stem().string();
}