#include <napi.h>
#include <string>
#include <sstream>
#include <vector>

#ifdef _WIN32
#include <windows.h>
#include <shellapi.h>

std::wstring StringToWString(const std::string &input)
{
    if (input.empty())
    {
        return std::wstring();
    }

    int requiredSize = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, nullptr, 0);
    if (requiredSize <= 0)
    {
        throw std::runtime_error("Failed to calculate buffer size for wide string conversion");
    }

    std::vector<wchar_t> buffer(requiredSize);
    int result = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, buffer.data(), requiredSize);
    if (result <= 0)
    {
        throw std::runtime_error("Failed to convert string to wide string");
    }

    return std::wstring(buffer.data(), result - 1);
}

std::string WStringToString(const std::wstring &wstr)
{
    if (wstr.empty())
    {
        return std::string();
    }

    int requiredSize = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
    if (requiredSize <= 0)
    {
        throw std::runtime_error("Failed to calculate buffer size for string conversion");
    }

    std::vector<char> buffer(requiredSize);
    int result = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, buffer.data(), requiredSize, nullptr, nullptr);
    if (result <= 0)
    {
        throw std::runtime_error("Failed to convert wide string to string");
    }

    return std::string(buffer.data(), result - 1);
}

std::string GetLastErrorWithMessage()
{
    DWORD errorCode = GetLastError();
    if (errorCode == 0)
        return "No error.";

    LPWSTR messageBuffer = nullptr;
    DWORD size = FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPWSTR)&messageBuffer, 0, NULL);

    std::ostringstream stream;

    if (!size || !messageBuffer)
    {
        stream << "Error " << errorCode << " - Unable to get error message";
        return stream.str();
    }

    std::string errorMessage = WStringToString(messageBuffer);

    LocalFree(messageBuffer);

    stream << errorCode << " - " << errorMessage;
    return stream.str();
}
#endif

void Elevate(const Napi::CallbackInfo &info)
{
    Napi::Env env = info.Env();

#ifdef _WIN32
    // Validate arguments: path to executable, parameters, callback
    if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsFunction())
    {
        throw Napi::TypeError::New(env, "Expected arguments: string (executable path), string (parameters), function (callback)");
    }

    // Get arguments
    std::string exePath = info[0].As<Napi::String>().Utf8Value();
    std::string parameters = info[1].As<Napi::String>().Utf8Value();
    Napi::Function callback = info[2].As<Napi::Function>();

    try
    {
        std::wstring wExePath = StringToWString(exePath);
        std::wstring wParameters = StringToWString(parameters);

        SHELLEXECUTEINFOW sei = {0};
        sei.cbSize = sizeof(SHELLEXECUTEINFOW);
        sei.fMask = SEE_MASK_FLAG_NO_UI;
        sei.lpVerb = L"runas";
        sei.lpFile = wExePath.c_str();
        sei.lpParameters = wParameters.c_str();
        sei.nShow = SW_SHOWNORMAL;

        // Execute the command
        if (!ShellExecuteExW(&sei))
        {
            std::string errorDetails = GetLastErrorWithMessage();
            callback.Call({Napi::Error::New(env, errorDetails).Value()});
        }
        else
        {
            callback.Call({env.Null()});
        }
    }
    catch (const std::exception &e)
    {
        callback.Call({Napi::Error::New(env, e.what()).Value()});
    }
#else
    throw Napi::Error::New(env, "Elevate is not supported on this platform");
#endif
}

Napi::Object Init(Napi::Env env, Napi::Object exports)
{
    exports.Set(Napi::String::New(env, "elevate"), Napi::Function::New(env, Elevate));
    return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init);
