/* 
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
// Implementation referenced from https://github.com/bradhugh/node-dpapi

#include <napi.h>
#include <uv.h>
#include <Windows.h>
#include <dpapi.h>
#include <string>

Napi::Value ProtectDataCommon(bool protect, const Napi::CallbackInfo& info)
{
    Napi::Env env = info.Env();

    // Argüman sayısı kontrolü
    if (info.Length() != 3) {
        throw Napi::RangeError::New(env, "3 arguments are required");
    }

    // 1. argüman: Uint8Array / Buffer kontrolü
    if (info[0].IsNull() || info[0].IsUndefined() || !info[0].IsTypedArray() || info[0].As<Napi::TypedArray>().TypedArrayType() != napi_uint8_array) {
        throw Napi::TypeError::New(env, "First argument, data, must be a valid Uint8Array");
    }

    // 2. argüman: Entropy, null veya Uint8Array olmalı
    if (!info[1].IsNull() && (!info[1].IsTypedArray() || info[1].As<Napi::TypedArray>().TypedArrayType() != napi_uint8_array)) {
        throw Napi::TypeError::New(env, "Second argument, optionalEntropy, must be null or a Uint8Array");
    }

    // 3. argüman: Scope string olmalı
    if (info[2].IsNull() || info[2].IsUndefined() || !info[2].IsString()) {
        throw Napi::TypeError::New(env, "Third argument, scope, must be a string");
    }

    DWORD flags = 0;
    std::string scope = info[2].As<Napi::String>().Utf8Value();

    if (scope == "LocalMachine") {
        flags = CRYPTPROTECT_LOCAL_MACHINE;
    }
    else if (scope != "CurrentUser") {
        throw Napi::TypeError::New(env, "Scope must be 'CurrentUser' or 'LocalMachine'");
    }

    // Buffer ve uzunluk al
    auto buffer = info[0].As<Napi::Buffer<uint8_t>>().Data();
    auto len = info[0].As<Napi::Buffer<uint8_t>>().Length();

    // Entropy ayarla
    DATA_BLOB entropyBlob;
    entropyBlob.pbData = nullptr;
    entropyBlob.cbData = 0;
    if (!info[1].IsNull()) {
        entropyBlob.pbData = reinterpret_cast<BYTE*>(info[1].As<Napi::Buffer<uint8_t>>().Data());
        entropyBlob.cbData = info[1].As<Napi::Buffer<uint8_t>>().Length();
    }

    DATA_BLOB dataIn;
    DATA_BLOB dataOut;

    dataIn.pbData = reinterpret_cast<BYTE*>(buffer);
    dataIn.cbData = len;

    bool success = false;

    if (protect) {
        success = CryptProtectData(
            &dataIn,
            nullptr, // description string
            entropyBlob.pbData ? &entropyBlob : nullptr,
            nullptr,
            nullptr,
            flags,
            &dataOut);
    }
    else {
        success = CryptUnprotectData(
            &dataIn,
            nullptr,
            entropyBlob.pbData ? &entropyBlob : nullptr,
            nullptr,
            nullptr,
            flags,
            &dataOut);
    }

    if (!success) {
        DWORD errorCode = GetLastError();
        std::string errorMessage = "Encryption/Decryption failed. Error code: " + std::to_string(errorCode);
        throw Napi::Error::New(env, errorMessage);
    }

    // Sonucu Node.js tarafına kopyala ve geri döndür
    Napi::Buffer<uint8_t> returnBuffer = Napi::Buffer<uint8_t>::Copy(env, dataOut.pbData, dataOut.cbData);
    LocalFree(dataOut.pbData);

    return returnBuffer;
}
