#include "MigrationManager.hpp"
#include <fstream>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <cstdlib>

MigrationManager::MigrationManager() {
    const char* home = std::getenv("HOME");
    if (home) {
        configDir_ = std::filesystem::path(home) / ".config" / "getpkg";
        packagesDir_ = configDir_ / PACKAGES_DIRECTORY_NAME;
        backupDir_ = configDir_ / BACKUP_DIRECTORY_NAME;
        legacyTokenDir_ = configDir_ / DEFAULT_SERVER_URL;
        
        packageManager_ = std::make_unique<PackageMetadataManager>(configDir_);
        serverManager_ = std::make_unique<ServerManager>();
    }
}

MigrationManager::MigrationManager(const std::filesystem::path& configDir) 
    : configDir_(configDir), 
      packagesDir_(configDir / PACKAGES_DIRECTORY_NAME),
      backupDir_(configDir / BACKUP_DIRECTORY_NAME),
      legacyTokenDir_(configDir / DEFAULT_SERVER_URL) {
    
    packageManager_ = std::make_unique<PackageMetadataManager>(configDir);
    serverManager_ = std::make_unique<ServerManager>();
}

bool MigrationManager::needsMigration() const {
    // Check if we have legacy configuration that needs migration
    bool hasLegacyConfig = hasLegacyServerConfiguration() || hasLegacyPackageFiles();
    bool hasNewConfig = hasNewFormatConfiguration();
    bool hasPackagesDir = std::filesystem::exists(packagesDir_);
    
    // Need migration if:
    // 1. We have legacy config (token file or package files in root config dir)
    // 2. We have new config but no packages directory (incomplete migration)
    return hasLegacyConfig || (hasNewConfig && !hasPackagesDir);
}

bool MigrationManager::performMigration() {
    lastResult_ = MigrationResult();
    
    logInfo("Starting migration from single-server to multi-server configuration");
    
    // Create backup before starting migration
    if (!createBackup()) {
        logError("Failed to create backup before migration");
        lastResult_.success = false;
        return false;
    }
    
    try {
        // Step 1: Create packages directory
        if (!createPackagesDirectory()) {
            logError("Failed to create packages directory");
            lastResult_.success = false;
            return false;
        }
        lastResult_.packageDirectoryCreated = true;
        
        // Step 2: Migrate server configuration
        if (!migrateServerConfiguration()) {
            logError("Failed to migrate server configuration");
            lastResult_.success = false;
            return false;
        }
        lastResult_.serverConfigMigrated = true;
        
        // Step 3: Migrate package metadata
        if (!migratePackageMetadata()) {
            logError("Failed to migrate package metadata");
            lastResult_.success = false;
            return false;
        }
        
        // Step 4: Validate migration
        if (!validateMigration()) {
            logError("Migration validation failed");
            lastResult_.success = false;
            return false;
        }
        
        // Step 5: Clean up legacy files (optional, keep backup)
        // We don't delete legacy files immediately to allow rollback
        
        lastResult_.success = true;
        logInfo("Migration completed successfully");
        
        return true;
        
    } catch (const std::exception& e) {
        logError("Migration failed with exception: " + std::string(e.what()));
        lastResult_.success = false;
        return false;
    }
}

bool MigrationManager::migrateServerConfiguration() {
    try {
        // Load existing server configuration or create default
        if (!serverManager_->loadConfiguration()) {
            logWarning("Failed to load existing server configuration, creating default");
            serverManager_->ensureDefaultConfiguration();
        }
        
        // Migrate legacy token file if it exists
        if (!migrateLegacyTokenFile()) {
            logWarning("Failed to migrate legacy token file (may not exist)");
        }
        
        // Save the configuration to ensure it's in the new format
        if (!serverManager_->saveConfiguration()) {
            logError("Failed to save server configuration");
            return false;
        }
        
        logInfo("Server configuration migrated successfully");
        return true;
        
    } catch (const std::exception& e) {
        logError("Error migrating server configuration: " + std::string(e.what()));
        return false;
    }
}

bool MigrationManager::migratePackageMetadata() {
    try {
        // Find legacy package files in the config directory
        std::vector<std::filesystem::path> legacyFiles = findFilesWithExtension(configDir_, ".json");
        
        // Filter out non-package files
        std::vector<std::filesystem::path> packageFiles;
        for (const auto& file : legacyFiles) {
            std::string filename = file.filename().string();
            // Skip servers.json and any files already in packages directory
            if (filename != SERVERS_CONFIG_FILENAME && file.parent_path() == configDir_) {
                packageFiles.push_back(file);
            }
        }
        
        lastResult_.totalPackages = packageFiles.size();
        
        if (packageFiles.empty()) {
            logInfo("No legacy package files found to migrate");
            return true;
        }
        
        logInfo("Found " + std::to_string(packageFiles.size()) + " legacy package files to migrate");
        
        // Migrate each package file
        for (const auto& packageFile : packageFiles) {
            if (migrateLegacyPackageFile(packageFile)) {
                lastResult_.migratedPackages++;
                logInfo("Migrated package file: " + packageFile.filename().string());
            } else {
                logError("Failed to migrate package file: " + packageFile.filename().string());
            }
        }
        
        logInfo("Migrated " + std::to_string(lastResult_.migratedPackages) + " of " + 
                std::to_string(lastResult_.totalPackages) + " package files");
        
        return lastResult_.migratedPackages == lastResult_.totalPackages;
        
    } catch (const std::exception& e) {
        logError("Error migrating package metadata: " + std::string(e.what()));
        return false;
    }
}

bool MigrationManager::createPackagesDirectory() {
    return safeDirectoryCreate(packagesDir_);
}

bool MigrationManager::validateMigration() const {
    try {
        // Validate server configuration
        if (!validateServerConfiguration()) {
            return false;
        }
        
        // Validate package metadata
        if (!validatePackageMetadata()) {
            return false;
        }
        
        // Validate directory structure
        if (!validateDirectoryStructure()) {
            return false;
        }
        
        return true;
        
    } catch (const std::exception& e) {
        std::cerr << "Error during migration validation: " << e.what() << std::endl;
        return false;
    }
}

bool MigrationManager::canRollback() const {
    return std::filesystem::exists(backupDir_) && std::filesystem::is_directory(backupDir_);
}

bool MigrationManager::performRollback() {
    if (!canRollback()) {
        logError("Cannot rollback: no backup found");
        return false;
    }
    
    try {
        logInfo("Starting rollback to previous configuration");
        
        // Restore from backup
        if (!restoreFromBackup()) {
            logError("Failed to restore from backup");
            return false;
        }
        
        logInfo("Rollback completed successfully");
        return true;
        
    } catch (const std::exception& e) {
        logError("Rollback failed with exception: " + std::string(e.what()));
        return false;
    }
}

bool MigrationManager::createBackup() {
    try {
        // Create backup directory with timestamp
        std::string timestamp = generateBackupTimestamp();
        std::filesystem::path timestampedBackupDir = backupDir_ / timestamp;
        
        if (!safeDirectoryCreate(timestampedBackupDir)) {
            return false;
        }
        
        // Backup existing configuration files
        std::filesystem::path serversConfigPath = configDir_ / SERVERS_CONFIG_FILENAME;
        if (std::filesystem::exists(serversConfigPath)) {
            safeFileCopy(serversConfigPath, timestampedBackupDir / SERVERS_CONFIG_FILENAME);
        }
        
        // Backup legacy token directory
        if (std::filesystem::exists(legacyTokenDir_)) {
            std::filesystem::path backupTokenDir = timestampedBackupDir / DEFAULT_SERVER_URL;
            safeDirectoryCreate(backupTokenDir);
            
            for (const auto& entry : std::filesystem::directory_iterator(legacyTokenDir_)) {
                if (entry.is_regular_file()) {
                    safeFileCopy(entry.path(), backupTokenDir / entry.path().filename());
                }
            }
        }
        
        // Backup existing package files
        std::vector<std::filesystem::path> packageFiles = findFilesWithExtension(configDir_, ".json");
        for (const auto& file : packageFiles) {
            if (file.parent_path() == configDir_) {
                safeFileCopy(file, timestampedBackupDir / file.filename());
            }
        }
        
        // Backup packages directory if it exists
        if (std::filesystem::exists(packagesDir_)) {
            std::filesystem::path backupPackagesDir = timestampedBackupDir / PACKAGES_DIRECTORY_NAME;
            safeDirectoryCreate(backupPackagesDir);
            
            for (const auto& entry : std::filesystem::directory_iterator(packagesDir_)) {
                if (entry.is_regular_file()) {
                    safeFileCopy(entry.path(), backupPackagesDir / entry.path().filename());
                }
            }
        }
        
        logInfo("Backup created at: " + timestampedBackupDir.string());
        return true;
        
    } catch (const std::exception& e) {
        logError("Failed to create backup: " + std::string(e.what()));
        return false;
    }
}

bool MigrationManager::restoreFromBackup() {
    try {
        // Find the most recent backup
        if (!std::filesystem::exists(backupDir_)) {
            return false;
        }
        
        std::filesystem::path latestBackup;
        std::filesystem::file_time_type latestTime{};
        
        for (const auto& entry : std::filesystem::directory_iterator(backupDir_)) {
            if (entry.is_directory()) {
                auto writeTime = entry.last_write_time();
                if (writeTime > latestTime) {
                    latestTime = writeTime;
                    latestBackup = entry.path();
                }
            }
        }
        
        if (latestBackup.empty()) {
            return false;
        }
        
        // Restore files from backup
        for (const auto& entry : std::filesystem::directory_iterator(latestBackup)) {
            std::filesystem::path targetPath = configDir_ / entry.path().filename();
            
            if (entry.is_regular_file()) {
                safeFileCopy(entry.path(), targetPath);
            } else if (entry.is_directory()) {
                // Restore directory recursively
                std::filesystem::remove_all(targetPath);
                std::filesystem::copy(entry.path(), targetPath, std::filesystem::copy_options::recursive);
            }
        }
        
        return true;
        
    } catch (const std::exception& e) {
        logError("Failed to restore from backup: " + std::string(e.what()));
        return false;
    }
}

// Private helper methods

bool MigrationManager::hasLegacyServerConfiguration() const {
    // Check for legacy token file
    std::filesystem::path legacyTokenPath = legacyTokenDir_ / LEGACY_TOKEN_FILENAME;
    return std::filesystem::exists(legacyTokenPath);
}

bool MigrationManager::hasLegacyPackageFiles() const {
    // Check for JSON files directly in config directory (not in packages subdirectory)
    std::vector<std::filesystem::path> jsonFiles = findFilesWithExtension(configDir_, ".json");
    
    for (const auto& file : jsonFiles) {
        std::string filename = file.filename().string();
        // If it's not servers.json and it's in the config directory (not packages), it's legacy
        if (filename != SERVERS_CONFIG_FILENAME && file.parent_path() == configDir_) {
            return true;
        }
    }
    
    return false;
}

bool MigrationManager::hasNewFormatConfiguration() const {
    std::filesystem::path serversConfigPath = configDir_ / SERVERS_CONFIG_FILENAME;
    return std::filesystem::exists(serversConfigPath);
}

bool MigrationManager::migrateLegacyTokenFile() {
    std::filesystem::path legacyTokenPath = legacyTokenDir_ / LEGACY_TOKEN_FILENAME;
    
    if (!std::filesystem::exists(legacyTokenPath)) {
        return true; // Nothing to migrate
    }
    
    try {
        std::ifstream tokenFile(legacyTokenPath);
        std::string token;
        std::getline(tokenFile, token);
        tokenFile.close();
        
        if (!token.empty()) {
            // Set the token for the default server
            if (serverManager_->setWriteToken(DEFAULT_SERVER_URL, token)) {
                logInfo("Migrated legacy write token for " + std::string(DEFAULT_SERVER_URL));
                
                // Move the legacy token file to backup (don't delete immediately)
                std::filesystem::path backupTokenPath = backupDir_ / "legacy_tokens" / DEFAULT_SERVER_URL / LEGACY_TOKEN_FILENAME;
                safeDirectoryCreate(backupTokenPath.parent_path());
                safeFileMove(legacyTokenPath, backupTokenPath);
                
                // Remove the legacy directory if it's empty
                try {
                    if (std::filesystem::is_empty(legacyTokenDir_)) {
                        std::filesystem::remove(legacyTokenDir_);
                    }
                } catch (const std::exception& e) {
                    // Ignore errors when removing empty directory
                }
                
                return true;
            }
        }
        
        return false;
        
    } catch (const std::exception& e) {
        logError("Failed to migrate legacy token file: " + std::string(e.what()));
        return false;
    }
}

bool MigrationManager::migrateLegacyPackageFile(const std::filesystem::path& legacyPath) {
    try {
        if (!std::filesystem::exists(legacyPath)) {
            return false;
        }
        
        // Load legacy format
        std::ifstream file(legacyPath);
        if (!file.is_open()) {
            logError("Failed to open legacy file: " + legacyPath.string());
            return false;
        }
        
        nlohmann::json legacyJson;
        file >> legacyJson;
        file.close();
        
        // Convert to new format
        PackageMetadata metadata = PackageMetadata::fromLegacyJson(legacyJson, DEFAULT_SERVER_URL);
        
        if (!metadata.isValid()) {
            logError("Invalid metadata after migration from " + legacyPath.string() + ": " + metadata.getValidationError());
            return false;
        }
        
        // Save in new location
        if (!packageManager_->savePackageMetadata(metadata)) {
            logError("Failed to save migrated metadata for " + metadata.name);
            return false;
        }
        
        // Move legacy file to backup (don't delete immediately)
        std::filesystem::path backupPath = backupDir_ / "legacy_packages" / legacyPath.filename();
        safeDirectoryCreate(backupPath.parent_path());
        safeFileMove(legacyPath, backupPath);
        
        return true;
        
    } catch (const std::exception& e) {
        logError("Error migrating legacy file " + legacyPath.string() + ": " + std::string(e.what()));
        return false;
    }
}

bool MigrationManager::validateServerConfiguration() const {
    try {
        // Check if servers.json exists and is valid
        std::filesystem::path serversConfigPath = configDir_ / SERVERS_CONFIG_FILENAME;
        if (!std::filesystem::exists(serversConfigPath)) {
            return false;
        }
        
        // Try to load the configuration
        auto tempServerManager = std::make_unique<ServerManager>();
        if (!tempServerManager->loadConfiguration()) {
            return false;
        }
        
        // Check that we have at least one server
        std::vector<std::string> servers = tempServerManager->getServers();
        return !servers.empty();
        
    } catch (const std::exception& e) {
        return false;
    }
}

bool MigrationManager::validatePackageMetadata() const {
    try {
        if (!std::filesystem::exists(packagesDir_)) {
            return false;
        }
        
        // Validate all package metadata files
        return packageManager_->validateAllPackageMetadata();
        
    } catch (const std::exception& e) {
        return false;
    }
}

bool MigrationManager::validateDirectoryStructure() const {
    // Check that packages directory exists and is accessible
    return std::filesystem::exists(packagesDir_) && std::filesystem::is_directory(packagesDir_);
}

void MigrationManager::logError(const std::string& message) const {
    std::cerr << "[MIGRATION ERROR] " << message << std::endl;
    lastResult_.errors.push_back(message);
}

void MigrationManager::logWarning(const std::string& message) const {
    std::cerr << "[MIGRATION WARNING] " << message << std::endl;
    lastResult_.warnings.push_back(message);
}

void MigrationManager::logInfo(const std::string& message) const {
    std::cout << "[MIGRATION INFO] " << message << std::endl;
}

bool MigrationManager::safeFileMove(const std::filesystem::path& source, const std::filesystem::path& destination) {
    try {
        // Ensure destination directory exists
        std::filesystem::create_directories(destination.parent_path());
        
        // Move the file
        std::filesystem::rename(source, destination);
        return true;
        
    } catch (const std::exception& e) {
        logError("Failed to move file from " + source.string() + " to " + destination.string() + ": " + e.what());
        return false;
    }
}

bool MigrationManager::safeFileCopy(const std::filesystem::path& source, const std::filesystem::path& destination) {
    try {
        // Ensure destination directory exists
        std::filesystem::create_directories(destination.parent_path());
        
        // Copy the file
        std::filesystem::copy_file(source, destination, std::filesystem::copy_options::overwrite_existing);
        return true;
        
    } catch (const std::exception& e) {
        logError("Failed to copy file from " + source.string() + " to " + destination.string() + ": " + e.what());
        return false;
    }
}

bool MigrationManager::safeDirectoryCreate(const std::filesystem::path& directory) {
    try {
        std::filesystem::create_directories(directory);
        return std::filesystem::exists(directory) && std::filesystem::is_directory(directory);
        
    } catch (const std::exception& e) {
        logError("Failed to create directory " + directory.string() + ": " + e.what());
        return false;
    }
}

std::vector<std::filesystem::path> MigrationManager::findFilesWithExtension(const std::filesystem::path& directory, const std::string& extension) const {
    std::vector<std::filesystem::path> files;
    
    try {
        if (!std::filesystem::exists(directory)) {
            return files;
        }
        
        for (const auto& entry : std::filesystem::directory_iterator(directory)) {
            if (entry.is_regular_file() && entry.path().extension() == extension) {
                files.push_back(entry.path());
            }
        }
    } catch (const std::exception& e) {
        logError("Error finding files with extension " + extension + " in " + directory.string() + ": " + e.what());
    }
    
    return files;
}

std::string MigrationManager::generateBackupTimestamp() 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%d_%H%M%S");
    return ss.str();
}