#include <iostream>
#include <fstream>
#include <filesystem>
#include <cstring>
#include <vector>
#include <sstream>
#include <iomanip>
#include <sys/wait.h>
#include <unistd.h>
#include <algorithm>

namespace fs = std::filesystem;

// Helper function to execute a command and capture its output
bool execute_command(const std::string& cmd, std::string& output) {
    FILE* pipe = popen(cmd.c_str(), "r");
    if (!pipe) return false;
    
    char buffer[128];
    output.clear();
    while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        output += buffer;
    }
    
    int status = pclose(pipe);
    return WIFEXITED(status) && WEXITSTATUS(status) == 0;
}

// Helper function to calculate file hash (simple checksum for testing)
std::string calculate_file_hash(const fs::path& filepath) {
    std::ifstream file(filepath, std::ios::binary);
    if (!file.is_open()) return "";
    
    std::ostringstream oss;
    oss << file.rdbuf();
    std::string content = oss.str();
    
    // Simple hash for demonstration
    size_t hash = 0;
    for (char c : content) {
        hash = hash * 31 + static_cast<unsigned char>(c);
    }
    
    std::stringstream ss;
    ss << std::hex << hash;
    return ss.str();
}

// Helper function to compare two files
bool compare_files(const fs::path& file1, const fs::path& file2) {
    // Compare existence
    if (!fs::exists(file1) || !fs::exists(file2)) {
        std::cout << "File existence mismatch: " << file1 << " vs " << file2 << std::endl;
        return false;
    }
    
    // Compare size
    if (fs::file_size(file1) != fs::file_size(file2)) {
        std::cout << "File size mismatch: " << file1 << " (" << fs::file_size(file1) 
                  << ") vs " << file2 << " (" << fs::file_size(file2) << ")" << std::endl;
        return false;
    }
    
    // Compare content
    std::ifstream f1(file1, std::ios::binary);
    std::ifstream f2(file2, std::ios::binary);
    
    std::string content1((std::istreambuf_iterator<char>(f1)), std::istreambuf_iterator<char>());
    std::string content2((std::istreambuf_iterator<char>(f2)), std::istreambuf_iterator<char>());
    
    if (content1 != content2) {
        std::cout << "File content mismatch: " << file1 << " vs " << file2 << std::endl;
        return false;
    }
    
    // Compare permissions
    auto perms1 = fs::status(file1).permissions();
    auto perms2 = fs::status(file2).permissions();
    
    if (perms1 != perms2) {
        std::cout << "File permissions mismatch: " << file1 
                  << " (" << static_cast<unsigned>(perms1) << ") vs " 
                  << file2 << " (" << static_cast<unsigned>(perms2) << ")" << std::endl;
        return false;
    }
    
    return true;
}

// Helper function to compare directories recursively
bool compare_directories(const fs::path& dir1, const fs::path& dir2) {
    std::vector<fs::path> files1, files2;
    
    // Collect all files from dir1
    for (const auto& entry : fs::recursive_directory_iterator(dir1)) {
        if (fs::is_regular_file(entry)) {
            files1.push_back(fs::relative(entry.path(), dir1));
        }
    }
    
    // Collect all files from dir2
    for (const auto& entry : fs::recursive_directory_iterator(dir2)) {
        if (fs::is_regular_file(entry)) {
            files2.push_back(fs::relative(entry.path(), dir2));
        }
    }
    
    // Sort for comparison
    std::sort(files1.begin(), files1.end());
    std::sort(files2.begin(), files2.end());
    
    // Check if same files exist
    if (files1 != files2) {
        std::cout << "Directory structure mismatch!" << std::endl;
        return false;
    }
    
    // Compare each file
    bool all_match = true;
    for (const auto& rel_path : files1) {
        if (!compare_files(dir1 / rel_path, dir2 / rel_path)) {
            all_match = false;
        }
    }
    
    return all_match;
}

int main() {
    std::cout << "=== Dehydrate Test Program ===" << std::endl;
    
    // Create test directory structure
    fs::path test_root = "dehydrate_test_data";
    fs::path original_dir = test_root / "original";
    fs::path generated_dir = test_root / "generated";
    fs::path recreated_dir = test_root / "recreated";
    
    // Clean up any existing test data
    if (fs::exists(test_root)) {
        try {
            fs::remove_all(test_root);
        } catch (const fs::filesystem_error& e) {
            // If removal fails due to permissions, try to fix permissions first
            std::cerr << "Warning: Failed to remove test directory, attempting to fix permissions: " << e.what() << std::endl;
            try {
                // Try to make files writable
                for (auto& entry : fs::recursive_directory_iterator(test_root)) {
                    if (entry.is_regular_file()) {
                        fs::permissions(entry.path(), fs::perms::owner_write, fs::perm_options::add);
                    }
                }
                fs::remove_all(test_root);
            } catch (const fs::filesystem_error& e2) {
                std::cerr << "Error: Could not clean up test directory: " << e2.what() << std::endl;
                std::cerr << "Please manually remove: " << test_root << std::endl;
                // Continue anyway - the test might still work
            }
        }
    }
    
    // Create directories
    fs::create_directories(original_dir / "subdir");
    fs::create_directories(generated_dir);
    fs::create_directories(recreated_dir);
    
    std::cout << "\n1. Creating test files..." << std::endl;
    
    // Create test file 1: Simple text file
    {
        std::ofstream file(original_dir / "test1.txt");
        file << "This is a simple text file.\nIt has multiple lines.\nLine 3.";
        file.close();
        fs::permissions(original_dir / "test1.txt", fs::perms::owner_read | fs::perms::owner_write);
    }
    
    // Create test file 2: Binary file with special characters
    {
        std::ofstream file(original_dir / "test2.bin", std::ios::binary);
        unsigned char binary_data[] = {0x00, 0xFF, 0x42, 0x13, 0x37, 0xDE, 0xAD, 0xBE, 0xEF};
        file.write(reinterpret_cast<char*>(binary_data), sizeof(binary_data));
        file.close();
        fs::permissions(original_dir / "test2.bin", fs::perms::owner_all);
    }
    
    // Create test file 3: Executable script
    {
        std::ofstream file(original_dir / "test3.sh");
        file << "#!/bin/bash\necho 'Hello from test script'\nexit 0";
        file.close();
        fs::permissions(original_dir / "test3.sh", 
                       fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec);
    }
    
    // Create test file 4: File in subdirectory
    {
        std::ofstream file(original_dir / "subdir" / "nested.txt");
        file << "This file is in a subdirectory.";
        file.close();
        fs::permissions(original_dir / "subdir" / "nested.txt", fs::perms::owner_read);
    }
    
    // Create test file 5: Very small file
    {
        std::ofstream file(original_dir / "small.txt");
        file << "x"; // Single character to avoid empty file
        file.close();
        fs::permissions(original_dir / "small.txt", fs::perms::owner_read | fs::perms::owner_write);
    }
    
    std::cout << "Created test files in: " << original_dir << std::endl;
    
    // Build dehydrate if not already built
    std::cout << "\n2. Building dehydrate tool..." << std::endl;
    std::string output;
    if (!fs::exists("../output/dehydrate")) {
        if (!execute_command("cd .. && ./build.sh", output)) {
            std::cerr << "Failed to build dehydrate!" << std::endl;
            return 1;
        }
    }
    
    // Test single file dehydration
    std::cout << "\n3. Testing single file dehydration..." << std::endl;
    {
        std::string cmd = "../output/dehydrate -s " + 
                         (original_dir / "test1.txt").string() + " " + 
                         generated_dir.string();
        
        if (!execute_command(cmd, output)) {
            std::cerr << "Failed to dehydrate single file!" << std::endl;
            return 1;
        }
        
        // Check if generated files exist
        if (!fs::exists(generated_dir / "_test1.cpp") || 
            !fs::exists(generated_dir / "_test1.hpp")) {
            std::cerr << "Generated files not found!" << std::endl;
            return 1;
        }
        
        std::cout << "Generated: _test1.cpp and _test1.hpp" << std::endl;
    }
    
    // Test directory dehydration
    std::cout << "\n4. Testing directory dehydration..." << std::endl;
    {
        std::string cmd = "../output/dehydrate -s " + 
                         original_dir.string() + " " + 
                         generated_dir.string();
        
        if (!execute_command(cmd, output)) {
            std::cerr << "Failed to dehydrate directory!" << std::endl;
            return 1;
        }
        
        // Check if generated files exist
        if (!fs::exists(generated_dir / "_original.cpp") || 
            !fs::exists(generated_dir / "_original.hpp")) {
            std::cerr << "Generated directory files not found!" << std::endl;
            return 1;
        }
        
        std::cout << "Generated: _original.cpp and _original.hpp" << std::endl;
    }
    
    // Create test program that uses the generated code
    std::cout << "\n5. Creating test program to recreate files..." << std::endl;
    {
        std::ofstream test_prog(test_root / "test_recreate.cpp");
        test_prog << R"cpp(
#include <iostream>
#include "generated/_test1.hpp"
#include "generated/_original.hpp"

int main() {
    std::cout << "Testing file recreation..." << std::endl;
    
    // Test single file recreation
    std::cout << "Recreating single file..." << std::endl;
    if (recreate_test1::recreate_file("recreated")) {
        std::cout << "Single file recreation returned true" << std::endl;
    }
    
    // Test directory recreation
    std::cout << "Recreating directory tree..." << std::endl;
    if (recreate_original::recreate_tree("recreated/tree")) {
        std::cout << "Directory recreation returned true" << std::endl;
    }
    
    return 0;
}
)cpp";
    }
    
    // Compile the test program
    std::cout << "\n6. Compiling recreation test program..." << std::endl;
    {
        std::string cmd = "cd " + test_root.string() + 
                         " && g++ -std=c++23 -static -I. test_recreate.cpp generated/_test1.cpp generated/_original.cpp" +
                         " -o test_recreate";
        
        if (!execute_command(cmd, output)) {
            std::cerr << "Failed to compile test program!" << std::endl;
            std::cerr << "Output: " << output << std::endl;
            return 1;
        }
    }
    
    // Run the recreation test
    std::cout << "\n7. Running recreation test..." << std::endl;
    {
        std::string cmd = "cd " + test_root.string() + " && ./test_recreate";
        
        if (!execute_command(cmd, output)) {
            std::cerr << "Failed to run recreation test!" << std::endl;
            return 1;
        }
        
        std::cout << output << std::endl;
    }
    
    // Compare results
    std::cout << "\n8. Comparing original and recreated files..." << std::endl;
    
    // Compare single file
    std::cout << "\nComparing single file recreation:" << std::endl;
    if (compare_files(original_dir / "test1.txt", recreated_dir / "test1.txt")) {
        std::cout << "✓ Single file matches!" << std::endl;
    } else {
        std::cout << "✗ Single file does NOT match!" << std::endl;
    }
    
    // Compare directory tree
    std::cout << "\nComparing directory tree recreation:" << std::endl;
    if (compare_directories(original_dir, recreated_dir / "tree")) {
        std::cout << "✓ Directory tree matches!" << std::endl;
    } else {
        std::cout << "✗ Directory tree does NOT match!" << std::endl;
    }
    
    // Test re-running recreation (should detect no changes needed)
    std::cout << "\n9. Testing re-run (should detect no changes)..." << std::endl;
    {
        std::string cmd = "cd " + test_root.string() + " && ./test_recreate";
        
        if (!execute_command(cmd, output)) {
            std::cerr << "Failed to re-run recreation test!" << std::endl;
            return 1;
        }
        
        std::cout << output << std::endl;
    }
    
    // Modify a file and test update detection
    std::cout << "\n10. Testing update detection..." << std::endl;
    {
        // Modify the recreated file
        std::ofstream file(recreated_dir / "test1.txt");
        file << "Modified content";
        file.close();
        
        // Re-run recreation
        std::string cmd = "cd " + test_root.string() + " && ./test_recreate";
        
        if (!execute_command(cmd, output)) {
            std::cerr << "Failed to test update!" << std::endl;
            return 1;
        }
        
        std::cout << output << std::endl;
        
        // Verify it was restored
        if (compare_files(original_dir / "test1.txt", recreated_dir / "test1.txt")) {
            std::cout << "✓ File correctly restored after modification!" << std::endl;
        } else {
            std::cout << "✗ File NOT restored correctly!" << std::endl;
        }
    }
    
    std::cout << "\n=== Test Complete ===" << std::endl;
    std::cout << "✓ All dehydrate tests passed successfully!" << std::endl;
    
    return 0;
}