#include "engine_vault.h"

#include <array>
#include <memory>
#include <fstream>
#include <iostream>
#include <dirent.h>
#include <unistd.h>
#include <cstring>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <sodium.h>

namespace node669427 {

Vault::Vault(const std::string& vault_path, const std::string& key_path)
    : vault_path_(vault_path), key_path_(key_path) {}

Vault::~Vault() {
    std::lock_guard<std::mutex> lock(cache_mutex_);
    for (auto& [k, v] : cache_) {
        sodium_memzero(&v[0], v.size());
    }
    cache_.clear();
}

bool Vault::initialize() {
    if (access(vault_path_.c_str(), R_OK) != 0) {
        std::cerr << "[Vault] Vault path not accessible: " << vault_path_ << std::endl;
        return false;
    }
    if (access(key_path_.c_str(), R_OK) != 0) {
        std::cerr << "[Vault] Key file not accessible: " << key_path_ << std::endl;
        return false;
    }
    scanAndDecrypt();
    return !cache_.empty();
}

std::string Vault::decryptFile(const std::string& file_path) const {
    // 1. Read the key file
    std::ifstream kf(key_path_);
    if (!kf.is_open()) return "";
    std::string pass;
    std::getline(kf, pass);
    if (pass.empty()) return "";

    // 2. Read the encrypted file
    std::ifstream f(file_path, std::ios::binary);
    if (!f.is_open()) {
        sodium_memzero(&pass[0], pass.size());
        return "";
    }
    std::string ciphertext((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
    
    // Check for "Salted__" header
    if (ciphertext.size() < 16 || ciphertext.substr(0, 8) != "Salted__") {
        sodium_memzero(&pass[0], pass.size());
        return "";
    }
    
    unsigned char salt[8];
    std::memcpy(salt, ciphertext.data() + 8, 8);
    
    // 3. Derive key and IV (PBKDF2 iter 100000, AES-256-CBC)
    unsigned char key[32];
    unsigned char iv[16];
    
    if (!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), salt, 
                        reinterpret_cast<const unsigned char*>(pass.data()), static_cast<int>(pass.size()), 
                        100000, key, iv)) {
        sodium_memzero(&pass[0], pass.size());
        return "";
    }
    sodium_memzero(&pass[0], pass.size());
    
    // 4. Decrypt
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) return "";
    
    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key, iv) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return "";
    }
    
    std::string plaintext;
    plaintext.resize(ciphertext.size()); // Max possible size
    int out_len1 = 0;
    
    if (EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(&plaintext[0]), &out_len1,
                          reinterpret_cast<const unsigned char*>(ciphertext.data()) + 16, 
                          static_cast<int>(ciphertext.size() - 16)) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return "";
    }
    
    int out_len2 = 0;
    if (EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(&plaintext[0]) + out_len1, &out_len2) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        return "";
    }
    
    plaintext.resize(out_len1 + out_len2);
    EVP_CIPHER_CTX_free(ctx);
    sodium_memzero(key, sizeof(key));
    sodium_memzero(iv, sizeof(iv));
    
    // Trim trailing newline if present
    if (!plaintext.empty() && plaintext.back() == '\n') plaintext.pop_back();
    return plaintext;
}

void Vault::scanAndDecrypt() {
    std::lock_guard<std::mutex> lock(cache_mutex_);
    DIR* dir = opendir(vault_path_.c_str());
    if (!dir) return;
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        std::string name = entry->d_name;
        if (name == "." || name == "..") continue;
        if (name.size() < 7 || name.substr(name.size() - 6) != ".vault") continue;
        
        bool safe = true;
        for (char c : name) {
            if (!std::isalnum(c) && c != '_' && c != '-' && c != '.') {
                safe = false;
                break;
            }
        }
        if (!safe) continue;

        std::string file_path = vault_path_ + "/" + name;
        std::string decrypted = decryptFile(file_path);
        if (!decrypted.empty()) {
            std::string key = name.substr(0, name.size() - 6);
            cache_[key] = decrypted;
        }
    }
    closedir(dir);
}

std::string Vault::get(const std::string& key) const {
    std::lock_guard<std::mutex> lock(cache_mutex_);
    auto it = cache_.find(key);
    return (it != cache_.end()) ? it->second : "";
}

std::string Vault::getOrDefault(const std::string& key, const std::string& default_val) const {
    std::string val = get(key);
    return val.empty() ? default_val : val;
}

bool Vault::has(const std::string& key) const {
    std::lock_guard<std::mutex> lock(cache_mutex_);
    return cache_.find(key) != cache_.end();
}

void Vault::refresh() {
    scanAndDecrypt();
}

size_t Vault::size() const {
    std::lock_guard<std::mutex> lock(cache_mutex_);
    return cache_.size();
}

} // namespace node669427
