#include "generator.hpp"
#include "hash.hpp"
#include <iostream>
#include <fstream>
#include <filesystem>
#include <sstream>
#include <vector>
#include <sys/stat.h>  // For file permissions
#include <cstring>     // For strlen

namespace fs = std::filesystem;

static std::string sanitize(const std::string& name) {
    std::string out = name;
    for (char& c : out) if (!isalnum(c)) c = '_';
    return out;
}


static uint64_t fnv1a_64(const void* data, size_t len) {
    const uint8_t* p = static_cast<const uint8_t*>(data);
    uint64_t h = 0xcbf29ce484222325ULL;
    for (size_t i = 0; i < len; ++i)
        h = (h ^ p[i]) * 0x100000001b3ULL;
    return h;
}

// Base64 encoding function - no dependencies
static std::string base64_encode(const unsigned char* data, size_t len) {
    const char* base64_chars = 
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    std::string result;
    result.reserve((len + 2) / 3 * 4); // Reserve space for the full encoded size
    
    int val = 0, valb = -6;
    for (size_t i = 0; i < len; i++) {
        val = (val << 8) + data[i];
        valb += 8;
        while (valb >= 0) {
            result.push_back(base64_chars[(val >> valb) & 0x3F]);
            valb -= 6;
        }
    }
    
    if (valb > -6) {
        result.push_back(base64_chars[((val << 8) >> (valb + 8)) & 0x3F]);
    }
    
    // Add padding
    while (result.size() % 4) {
        result.push_back('=');
    }
    
    return result;
}

// Helper function to output the _recreate_file_ utility function and Base64 decoder
static void output_recreate_file_utility(std::ofstream& cpp) {
    cpp << R"cpp(
// Base64 decoding function - no dependencies
static void base64_decode(const char* encoded_data, size_t encoded_len, unsigned char* output, size_t* output_len) {
    const char* base64_chars = 
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    size_t out_pos = 0;
    int val = 0, valb = -8;
    
    for (size_t i = 0; i < encoded_len; i++) {
        char c = encoded_data[i];
        if (c == '=') break;
        
        // Find position in base64_chars
        const char* pos = strchr(base64_chars, c);
        if (pos == nullptr) continue; // Skip invalid characters
        
        val = (val << 6) + static_cast<int>(pos - base64_chars);
        valb += 6;
        if (valb >= 0) {
            output[out_pos++] = static_cast<unsigned char>((val >> valb) & 0xFF);
            valb -= 8;
        }
    }
    
    *output_len = out_pos;
}

// Utility function to recreate a file with proper permissions
static bool _recreate_file_(const std::filesystem::path& outpath, uint64_t file_hash, std::filesystem::perms file_perms, const unsigned char* filedata, size_t filedata_len) {
    namespace fs = std::filesystem;
    bool needs_write = false;
    
    // Check if file exists and has correct hash
    if (fs::exists(outpath)) {
        // Check content hash
        std::ifstream in(outpath, std::ios::binary);
        std::ostringstream oss;
        oss << in.rdbuf();
        std::string data = oss.str();
        uint64_t existing_hash = fnv1a_64(data.data(), data.size());
        needs_write = existing_hash != file_hash;
    } else {
        needs_write = true; // File doesn't exist, need to create it
    }

    bool needs_permission_update = true;
    if (!needs_write) { // we always update permissions if the file is written or changed. Othewise we check.
        fs::perms current_perms = fs::status(outpath).permissions();
        needs_permission_update = current_perms != file_perms;
    }

    if (needs_write) {
        bool existed = fs::exists(outpath);

        fs::create_directories(outpath.parent_path());
        std::ofstream out(outpath, std::ios::binary);
        out.write(reinterpret_cast<const char*>(filedata), filedata_len);
        out.close();
        // Set the file permissions
        fs::permissions(outpath, file_perms);
        
        if (!existed) {
            std::cout << "[dehydrate] " << outpath.filename() << ": created\n";
        } else {
            std::cout << "[dehydrate] " << outpath.filename() << ": updated (hash changed)\n";
        }
        return true;
    } 
    
    if (needs_permission_update) {
        // Update only permissions
        fs::permissions(outpath, file_perms);
        std::cout << "[dehydrate] " << outpath.filename() << ": updated (permissions changed)\n";
        return true;
    }
    
    return false;
}
)cpp";
}


void generate_file_code(const std::string& source, const std::string& destfolder, bool silent) {
    fs::path src(source);
    fs::path dest(destfolder);
    std::string ns = "recreate_" + sanitize(src.stem().string());
    std::string cppname = "_" + src.stem().string() + ".cpp";
    std::string hppname = "_" + src.stem().string() + ".hpp";
    std::string bothname = "_" + src.stem().string() + ".{cpp,hpp}";
    fs::create_directories(dest);
    std::ifstream in(source, std::ios::binary);
    std::ostringstream oss;
    oss << in.rdbuf();
    std::string filedata = oss.str();
    uint64_t hash = fnv1a_64(filedata.data(), filedata.size());
    
    // Get source file permissions
    fs::perms src_perms = fs::status(src).permissions();
    
    // Write HPP
    std::ofstream hpp(dest / hppname);
    hpp << "#pragma once\n#include <string>\nnamespace " << ns << " {\nbool recreate_file(std::string destination_folder);\n}\n";
    // Write CPP
    std::ofstream cpp(dest / cppname);
    cpp << R"cpp(#include <fstream>
#include <filesystem>
#include <string>
#include <iostream>
#include <cstring>

// Tiny dependency-free FNV-1a 64-bit hash
static uint64_t fnv1a_64(const void* data, size_t len) {
    const uint8_t* p = static_cast<const uint8_t*>(data);
    uint64_t h = 0xcbf29ce484222325ULL;
    for (size_t i = 0; i < len; ++i)
        h = (h ^ p[i]) * 0x100000001b3ULL;
    return h;
}
)cpp";
    cpp << "#include \"" << hppname << "\"\n";
    cpp << "namespace " << ns << " {\n";
    
    // Output the recreate_file utility function
    output_recreate_file_utility(cpp);
    
    // Write recreate_file function with embedded file data
    cpp << R"cpp(
bool recreate_file(std::string destination_folder) {
    namespace fs = std::filesystem;
    fs::path outpath = fs::path(destination_folder) / ")cpp" << src.filename().string() << R"cpp(";
    
    // File data embedded as Base64
    static const char filedata_base64[] = )cpp";
    
    // Encode the file data to Base64
    std::string base64 = base64_encode(reinterpret_cast<const unsigned char*>(filedata.data()), filedata.size());
    
    // Split into 76-character chunks for readability
    const size_t line_length = 76;
    for (size_t i = 0; i < base64.length(); i += line_length) {
        if (i > 0) cpp << "\n        ";
        cpp << "\"" << base64.substr(i, std::min(line_length, base64.length() - i)) << "\"";
        if (i + line_length < base64.length()) cpp << "\\";
    }
    cpp << ";\n\n";
    
    // Decode Base64 at runtime
    cpp << "    // Decode Base64 data\n";
    cpp << "    size_t decoded_size = (strlen(filedata_base64) * 3) / 4;\n";
    cpp << "    unsigned char* decoded_data = new unsigned char[decoded_size];\n";
    cpp << "    size_t actual_size;\n";
    cpp << "    base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);\n\n";
    
    // Call _recreate_file_ with the decoded data
    cpp << "    bool result = _recreate_file_(outpath, " << hash << "ULL, "
        << "std::filesystem::perms(" << static_cast<unsigned>(src_perms) << "), "
        << "decoded_data, actual_size);\n";
    
    // Clean up
    cpp << "    delete[] decoded_data;\n";
    cpp << "    return result;\n";
    cpp << "}\n";
    cpp << "}\n";
    
    if (!silent) {
        std::cout << "[dehydrate] Generated: " << (dest / bothname)  << std::endl;
    }
}

// Helper to recursively collect all files in a directory
template<typename F>
void walk_dir(const fs::path& dir, F&& f) {
    for (auto& p : fs::recursive_directory_iterator(dir)) {
        if (fs::is_regular_file(p)) f(p.path());
    }
}


void generate_folder_code(const std::string& source, const std::string& destfolder, bool silent) {
    fs::path src(source);
    fs::path dest(destfolder);
    std::string ns = "recreate_" + sanitize(src.stem().string());
    std::string cppname = "_" + src.stem().string() + ".cpp";
    std::string hppname = "_" + src.stem().string() + ".hpp";
    std::string bothname = "_" + src.stem().string() + ".{cpp,hpp}";
    fs::create_directories(dest);
    // Collect all files
    std::vector<fs::path> files;
    walk_dir(src, [&](const fs::path& p) { files.push_back(p); });
    // Write HPP
    std::ofstream hpp(dest / hppname);

    // -------------------------------------------------------------------------
    // Generate HPP
    hpp << R"hpp(
#pragma once

/*

   THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
   DO NOT EDIT THIS FILE.

*/


#include <string>
namespace )hpp" << ns << R"hpp( {
    bool recreate_tree(std::string destination_folder);
}
)hpp";


    // -------------------------------------------------------------------------
    // Write CPP
    std::ofstream cpp(dest / cppname);
    cpp << R"cpp(#include <fstream>
#include <filesystem>
#include <string>
#include <iostream>
#include <cstring>

/*

   THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
   DO NOT EDIT THIS FILE.

*/


)cpp";
    cpp << "#include \"" << hppname << "\"\n";
    cpp << "namespace " << ns << " {\n";

    cpp << R"cpp(

// Tiny dependency-free FNV-1a 64-bit hash
static uint64_t fnv1a_64(const void* data, size_t len) {
    const uint8_t* p = static_cast<const uint8_t*>(data);
    uint64_t h = 0xcbf29ce484222325ULL;
    for (size_t i = 0; i < len; ++i)
        h = (h ^ p[i]) * 0x100000001b3ULL;
    return h;
}

)cpp";

    // Output the recreate_file utility function
    output_recreate_file_utility(cpp);

    // Start writing recreate_tree - we'll embed file data directly in function
    cpp << R"cpp(
bool recreate_tree(std::string destination_folder) {
    namespace fs = std::filesystem;
    bool any_written = false;
)cpp";

    // Process each file
    for (const auto& file : files) {
        std::ifstream in(file, std::ios::binary);
        std::ostringstream oss;
        oss << in.rdbuf();
        std::string filedata = oss.str();
        uint64_t hash = fnv1a_64(filedata.data(), filedata.size());
        fs::perms file_perms = fs::status(file).permissions();
        std::string rel = fs::relative(file, src).string();
        std::string var = sanitize(rel);
        
        // Start a scope to limit data's lifetime
        cpp << "    {\n";
        cpp << "        // File: " << rel << "\n";
        cpp << "        fs::path outpath = fs::path(destination_folder) / \"" << rel << "\";\n";
        
        // Embed file data as Base64
        cpp << "        static const char filedata_base64[] = ";
        
        // Encode the file data to Base64
        std::string base64 = base64_encode(reinterpret_cast<const unsigned char*>(filedata.data()), filedata.size());
        
        // Split into 76-character chunks for readability
        const size_t line_length = 76;
        for (size_t i = 0; i < base64.length(); i += line_length) {
            if (i > 0) cpp << "\n            ";
            cpp << "\"" << base64.substr(i, std::min(line_length, base64.length() - i)) << "\"";
            if (i + line_length < base64.length()) cpp << "\\";
        }
        cpp << ";\n\n";
        
        // Decode Base64 at runtime
        cpp << "        // Decode Base64 data\n";
        cpp << "        size_t decoded_size = (strlen(filedata_base64) * 3) / 4;\n";
        cpp << "        unsigned char* decoded_data = new unsigned char[decoded_size];\n";
        cpp << "        size_t actual_size;\n";
        cpp << "        base64_decode(filedata_base64, strlen(filedata_base64), decoded_data, &actual_size);\n\n";
        
        // Call _recreate_file_ with the decoded data
        cpp << "        bool file_written = _recreate_file_(outpath, " 
             << hash << "ULL, std::filesystem::perms(" << static_cast<unsigned>(file_perms) << "), "
             << "decoded_data, actual_size);\n";
        
        // Clean up and update flag
        cpp << "        delete[] decoded_data;\n";
        cpp << "        any_written = any_written || file_written;\n";
        cpp << "    }\n"; // Close scope to free memory
    }
    
    cpp << "    return any_written;\n";
    cpp << "}\n";
    cpp << "}\n";
    
    if (!silent) {
        std::cout << "[dehydrate] Generated: " << (dest / bothname) << std::endl;
    }
} 