// This file is part of BOINC.
// https://boinc.berkeley.edu
// Copyright (C) 2026 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.

#pragma once

#include <NTSecAPI.h>

#include "win_util.h"

bool userExists(const std::string& username);
bool userCreate(const std::string& username, const std::string& password);
bool userDelete(const std::string& username);
wil::unique_sid getUserSid(const std::string& username);
std::string getSidStringFromSid(wil::unique_sid&& userSid);
wil::unique_sid getCurrentUserSid();
std::string getCurrentUserSidString();
bool isAccountMemberOfLocalGroup(const std::string& accountName,
    const std::string& groupName);
std::string getLocalizedGroupName(wil::unique_sid&& sid);
std::string getLocalizedUsersGroupName();
std::string getLocalizedAdministratorsGroupName();
bool localGroupExists(const std::string& groupName);
bool createLocalGroup(const std::string& groupName);
bool deleteLocalGroup(const std::string& groupName);
bool addUserToTheBuiltinAdministratorsGroup(wil::unique_sid&& userSid);

// Need these lines copied from the wil library
// to avoid symbol conflicts with the LSA functions.
typedef wil::unique_any<LSA_HANDLE,
    decltype(&::LsaClose), ::LsaClose> unique_hlsa;
using lsa_freemem_deleter = wil::function_deleter<
    decltype(&::LsaFreeMemory), LsaFreeMemory>;
template <typename T>
using unique_lsamem_ptr = wistd::unique_ptr<
    wil::details::ensure_trivially_destructible_t<T>, lsa_freemem_deleter>;
// End of lines copied from wil.

LSA_HANDLE GetPolicyHandle();
// unsafe: lifetime of the `str` should be
// the same or longer as a resulting LSA_UNICODE_STRING
LSA_UNICODE_STRING toLsaUnicodeString(const std::wstring& str);

std::vector<std::string> getAccountRights(const std::string& username);

template<typename, typename = void>
struct has_value_type : std::false_type {};

template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> :
    std::true_type {
};

template<
    typename C,
    typename T = typename C::value_type,
    typename = std::enable_if_t<
    has_value_type<C>::value&&
    std::is_convertible<T, std::string>::value
    >
>
bool setAccountRights(const std::string& username, const C& rights) {
    auto policyHandle = GetPolicyHandle();
    if (policyHandle == nullptr) {
        return false;
    }
    unique_hlsa pHandle(policyHandle);
    const auto sid = getUserSid(username);
    if (!sid.is_valid()) {
        return false;
    }
    std::vector<std::wstring> wRights;
    const auto existingRights = getAccountRights(username);
    for (const auto& right : existingRights) {
        if (std::find(rights.cbegin(), rights.cend(), right)
            == rights.cend()) {
            wRights.emplace_back(boinc_ascii_to_wide(right));
        }
    }
    std::vector<LSA_UNICODE_STRING> rightsToApply;
    rightsToApply.reserve(wRights.size());
    for (const auto& right : wRights) {
        rightsToApply.emplace_back(toLsaUnicodeString(right));
    }
    if (!rightsToApply.empty()) {
        const auto result =
            LsaRemoveAccountRights(policyHandle, sid.get(), FALSE,
                rightsToApply.data(), static_cast<ULONG>(rightsToApply.size()));
        if (result != STATUS_SUCCESS &&
            LsaNtStatusToWinError(result) != ERROR_NO_SUCH_PRIVILEGE) {
            return false;
        }
    }

    wRights.clear();
    for (const auto right : rights) {
        if (std::find(existingRights.cbegin(), existingRights.cend(), right) ==
            existingRights.cend()) {
            wRights.emplace_back(boinc_ascii_to_wide(right));
        }
    }
    rightsToApply.reserve(wRights.size());
    for (const auto& right : wRights) {
        rightsToApply.emplace_back(toLsaUnicodeString(right));
    }
    if (!rightsToApply.empty()) {
        const auto result = LsaAddAccountRights(policyHandle, sid.get(),
            rightsToApply.data(), static_cast<ULONG>(rightsToApply.size()));
        if (result != STATUS_SUCCESS &&
            LsaNtStatusToWinError(result) != ERROR_NO_SUCH_PRIVILEGE) {
            return false;
        }
    }

    return true;
}
