/* SPDX-License-Identifier: GPL-2.0
 *
 * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
 */

#include <winsock2.h>
#include <Windows.h>
#include <ws2ipdef.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <bcrypt.h>
#include <wincrypt.h>
#include <sysinfoapi.h>
#include <winternl.h>
#include <stdio.h>
#include <wbemcli.h>
#include "wireguard.h"
#include "common.h"
#include "tunnel.h"
#include "network.h"

#include <strsafe.h>
#include <spdlog/spdlog.h>

#pragma comment(lib, "Crypt32.lib")

#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x)   HeapFree(GetProcessHeap(), 0, (x))

static WIREGUARD_CREATE_ADAPTER_FUNC             *WireGuardCreateAdapter;
static WIREGUARD_OPEN_ADAPTER_FUNC               *WireGuardOpenAdapter;
static WIREGUARD_CLOSE_ADAPTER_FUNC              *WireGuardCloseAdapter;
static WIREGUARD_GET_ADAPTER_LUID_FUNC           *WireGuardGetAdapterLUID;
static WIREGUARD_GET_RUNNING_DRIVER_VERSION_FUNC *WireGuardGetRunningDriverVersion;
static WIREGUARD_DELETE_DRIVER_FUNC              *WireGuardDeleteDriver;
static WIREGUARD_SET_LOGGER_FUNC                 *WireGuardSetLogger;
static WIREGUARD_SET_ADAPTER_LOGGING_FUNC        *WireGuardSetAdapterLogging;
static WIREGUARD_GET_ADAPTER_STATE_FUNC          *WireGuardGetAdapterState;
static WIREGUARD_SET_ADAPTER_STATE_FUNC          *WireGuardSetAdapterState;
static WIREGUARD_GET_CONFIGURATION_FUNC          *WireGuardGetConfiguration;
static WIREGUARD_SET_CONFIGURATION_FUNC          *WireGuardSetConfiguration;

#if 0
static int InitializeWMI()
{
    IWbemLocator* p_instance_ = nullptr;
	IWbemServices* p_service_ = nullptr;
	IEnumWbemClassObject* p_enum_ = nullptr;
	IWbemClassObject *p_obj_ = nullptr;
	IWbemClassObject *p_config = nullptr;

	// Step 1: Initialize COM.
	HRESULT	hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
	if (FAILED(hres)) {
        SPDLOG_ERROR("CoInitializeEx Error: {0}", GetLastError());
		return false;
	}

	// Step 3:  Obtain the initial locator to WMI
	hres = CoCreateInstance(
		CLSID_WbemLocator,
		0,
		CLSCTX_INPROC_SERVER,
		IID_IWbemLocator,
		(LPVOID*)&p_instance_);	
	if (FAILED(hres)) {
		SPDLOG_ERROR("CoCreateInstance Error: {0}", GetLastError());
		return false;
	}

	// Step 4: Connect to the local root\cimv2 namespace and obtain pointer pSvc to make IWbemServices calls.
	hres = p_instance_->ConnectServer(
		_bstr_t(L"ROOT\\CIMV2"),
		NULL,
		NULL,
		0,
		NULL,
		0,
		0,
		&p_service_
	);
	//ASSERT_THROW(SUCCEEDED(hres), "ConnectServer failed");
	if (FAILED(hres)) {
#ifdef _DEBUG
		cout << "ConnectServer failed: " << hres << endl;
#endif
		return false;
	}

	// Step 5:  Set security levels for the proxy
	hres = CoSetProxyBlanket(
		p_service_,                        // Indicates the proxy to set
		RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx 
		RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx 
		NULL,                        // Server principal name 
		RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx 
		RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
		NULL,                        // client identity
		EOAC_NONE                    // proxy capabilities 
	);
	//ASSERT_THROW(SUCCEEDED(hres), "CoSetProxyBlanket failed");
	if (FAILED(hres)) {
#ifdef _DEBUG
		cout << "CoSetProxyBlanket failed: " << hres << endl;
#endif
		return false;
	}

	// 通过适配器名称来找到指定的适配器对象.
	CComBSTR TheQuery = L"SELECT * FROM Win32_NetworkAdapterConfiguration WHERE SettingID = \"";
	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conversion;
	TheQuery += conversion.from_bytes(key_).c_str();
	TheQuery += L"\"";
  // const BSTR lang = L"WQL";
	hres = p_service_->ExecQuery(
		SysAllocString(L"WQL"),
		// L"WQL",
		TheQuery,
		WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY,
		NULL,
		&p_enum_);
	//ASSERT_THROW(SUCCEEDED(hres), "ExecQuery failed");
	if (FAILED(hres)) {
#ifdef _DEBUG
		cout << "ExecQuery failed: " << hres << endl;
#endif
		return false;
	}
	
	// Get the adapter object.
	ULONG num = 0;
	hres = p_enum_->Next(WBEM_INFINITE, 1, &p_obj_, &num);
	//ASSERT_THROW(SUCCEEDED(hres), "Next failed");
	if (FAILED(hres)) {
#ifdef _DEBUG
		cout << "Next failed: " << hres << endl;
#endif
		return false;
	}

	//ASSERT_THROW(0 < num, "Next failed");
	if (num < 1) {
#ifdef _DEBUG
		cout << "Next failed num < 1" << endl;
#endif
		return false;
	}

	VariantInit(&path_);
	hres = p_obj_->Get(L"__PATH", 0, &path_, NULL, NULL);
	//ASSERT_THROW(SUCCEEDED(hres), "Get path failed");
	if (FAILED(hres)) {
#ifdef _DEBUG
		cout << "Get failed: " << hres << endl;
#endif
		return false;
	}
	hres = p_service_->GetObject(_bstr_t(L"Win32_NetworkAdapterConfiguration"), 0, NULL, &p_config, NULL);
	//ASSERT_THROW(SUCCEEDED(hres), "GetObject Win32_NetworkAdapterConfiguration failed");
	if (FAILED(hres)) {
#ifdef _DEBUG
		cout << "GetObject failed: " << hres << endl;
#endif
		return false;
	}
	is_init_ = true;
	return true;
}
#endif

static HMODULE InitializeWireGuardNT(void) {
    HMODULE WireGuardDll = LoadLibraryExW(L"wireguard.dll",
                                          nullptr,
                                          LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
    if (!WireGuardDll) {
        return nullptr;
    }
#define X(Name) ((*(FARPROC *)&Name = GetProcAddress(WireGuardDll, #Name)) == nullptr)
    if (X(WireGuardCreateAdapter) || X(WireGuardOpenAdapter) || X(WireGuardCloseAdapter) ||
        X(WireGuardGetAdapterLUID) || X(WireGuardGetRunningDriverVersion) || X(WireGuardDeleteDriver) ||
        X(WireGuardSetLogger) || X(WireGuardSetAdapterLogging) || X(WireGuardGetAdapterState) ||
        X(WireGuardSetAdapterState) || X(WireGuardGetConfiguration) || X(WireGuardSetConfiguration))
#undef X
    {
        DWORD LastError = GetLastError();
        FreeLibrary(WireGuardDll);
        SetLastError(LastError);
        return nullptr;
    }
    return WireGuardDll;
}

//
//static void CALLBACK ConsoleLogger(_In_ WIREGUARD_LOGGER_LEVEL Level,
//                                   _In_ DWORD64                Timestamp,
//                                   _In_z_ const WCHAR         *LogLine) {
//    SYSTEMTIME SystemTime;
//    GetSystemTime(&SystemTime);
//    //FileTimeToSystemTime((FILETIME *)&Timestamp, &SystemTime);
//    WCHAR LevelMarker;
//    switch (Level) {
//        case WIREGUARD_LOG_INFO:
//            LevelMarker = L'+';
//            break;
//        case WIREGUARD_LOG_WARN:
//            LevelMarker = L'-';
//            break;
//        case WIREGUARD_LOG_ERR:
//            LevelMarker = L'!';
//            break;
//        default:
//            return;
//    }
//    fwprintf(stderr,
//             L"%04u-%02u-%02u %02u:%02u:%02u.%04u [%c] %s\n",
//             SystemTime.wYear,
//             SystemTime.wMonth,
//             SystemTime.wDay,
//             SystemTime.wHour,
//             SystemTime.wMinute,
//             SystemTime.wSecond,
//             SystemTime.wMilliseconds,
//             LevelMarker,
//             LogLine);
//}

static DWORD64 Now() {
    return 0;
}

#if 0
static DWORD LogError(_In_z_ const WCHAR *Prefix, _In_ DWORD Error) {
    WCHAR tMsg[MAX_PATH];
    StringCbPrintfW(tMsg, MAX_PATH, L"Error: %d", Error);
    ConsoleLogger(WIREGUARD_LOG_ERR, Now(), tMsg);
    return Error;
}

static DWORD LogLastError(_In_z_ const WCHAR *Prefix) {
    DWORD LastError = GetLastError();
    LogError(Prefix, LastError);
    SetLastError(LastError);
    return LastError;
}

static void Log(_In_ WIREGUARD_LOGGER_LEVEL Level, _In_z_ const WCHAR *Format, ...) {
    WCHAR   LogLine[0x200];
    va_list args;
    va_start(args, Format);
    _vsnwprintf_s(LogLine, _countof(LogLine), _TRUNCATE, Format, args);
    va_end(args);
    ConsoleLogger(Level, Now(), LogLine);
}
#endif

_Must_inspect_result_ _Return_type_success_(return != FALSE) static BOOL
    GenerateKeyPair(_Out_writes_bytes_all_(WIREGUARD_KEY_LENGTH) BYTE PublicKey[WIREGUARD_KEY_LENGTH],
                    _Out_writes_bytes_all_(WIREGUARD_KEY_LENGTH) BYTE PrivateKey[WIREGUARD_KEY_LENGTH]) {
    BCRYPT_ALG_HANDLE Algorithm;
    BCRYPT_KEY_HANDLE Key;
    NTSTATUS          Status;

    struct {
        BCRYPT_ECCKEY_BLOB Header;
        BYTE               Public[32];
        BYTE               Unused[32];
        BYTE               Private[32];
    } ExportedKey;

    ULONG Bytes;

    Status = BCryptOpenAlgorithmProvider(&Algorithm, BCRYPT_ECDH_ALGORITHM, nullptr, 0);
    if (!NT_SUCCESS(Status)) {
        goto out;
    }

    Status = BCryptSetProperty(Algorithm,
                               BCRYPT_ECC_CURVE_NAME,
                               (PUCHAR)BCRYPT_ECC_CURVE_25519,
                               sizeof(BCRYPT_ECC_CURVE_25519),
                               0);
    if (!NT_SUCCESS(Status)) {
        goto cleanupProvider;
    }

    Status = BCryptGenerateKeyPair(Algorithm, &Key, 255, 0);
    if (!NT_SUCCESS(Status)) {
        goto cleanupProvider;
    }

    Status = BCryptFinalizeKeyPair(Key, 0);
    if (!NT_SUCCESS(Status)) {
        goto cleanupKey;
    }

    Status = BCryptExportKey(Key,
                             nullptr,
                             BCRYPT_ECCPRIVATE_BLOB,
                             (PUCHAR)&ExportedKey,
                             sizeof(ExportedKey),
                             &Bytes,
                             0);
    if (!NT_SUCCESS(Status)) {
        goto cleanupKey;
    }

    memcpy(PublicKey, ExportedKey.Public, WIREGUARD_KEY_LENGTH);
    memcpy(PrivateKey, ExportedKey.Private, WIREGUARD_KEY_LENGTH);
    SecureZeroMemory(&ExportedKey, sizeof(ExportedKey));

cleanupKey:
    BCryptDestroyKey(Key);
cleanupProvider:
    BCryptCloseAlgorithmProvider(Algorithm, 0);
out:
    SetLastError(RtlNtStatusToDosError(Status));
    return NT_SUCCESS(Status);
}

static HANDLE QuitEvent;

//static BOOL WINAPI CtrlHandler(_In_ DWORD CtrlType) {
//    switch (CtrlType) {
//        case CTRL_C_EVENT:
//        case CTRL_BREAK_EVENT:
//        case CTRL_CLOSE_EVENT:
//        case CTRL_LOGOFF_EVENT:
//        case CTRL_SHUTDOWN_EVENT:
//            SPDLOG_INFO("Cleaning up and shutting down");
//            SetEvent(QuitEvent);
//            return TRUE;
//    }
//    return FALSE;
//}

static BOOL GetEndPoint(const TCHAR *Input, _Out_ SOCKADDR_INET *ResolvedDemoServer) {
    SOCKET     Socket = INVALID_SOCKET;
    TCHAR      name[MAX_PATH];
    ADDRINFOA  Hints = {};
    ADDRINFOA *Resolution;

    StringCbCopy(name, MAX_PATH, Input);
    TCHAR *Colon1 = strchr(name, ':');
    *Colon1       = '\0';

    Hints.ai_family   = AF_UNSPEC;
    Hints.ai_socktype = SOCK_STREAM;
    Hints.ai_protocol = IPPROTO_TCP;

    if (GetAddrInfo(name, Colon1 + 1, &Hints, &Resolution)) {
        return FALSE;
    }

    for (const ADDRINFOA *Candidate = Resolution; Candidate; Candidate = Candidate->ai_next) {
        UINT16 port;
        if (Candidate->ai_family != AF_INET && Candidate->ai_family != AF_INET6) {
            continue;
        }
        memcpy(ResolvedDemoServer, Candidate->ai_addr, Candidate->ai_addrlen);
        port = static_cast<UINT16>(strtoul(Colon1 + 1, nullptr, 10));

        if (ResolvedDemoServer->si_family == AF_INET) {
            ResolvedDemoServer->Ipv4.sin_port = htons(port);
        } else if (ResolvedDemoServer->si_family == AF_INET6) {
            ResolvedDemoServer->Ipv6.sin6_port = htons(port);
        }

        closesocket(Socket);
        FreeAddrInfo(Resolution);
        return TRUE;
    }

    closesocket(Socket);
    FreeAddrInfo(Resolution);
    return FALSE;
}

_Return_type_success_(return != FALSE) static BOOL TalkToDemoServer(_In_reads_bytes_(InputLength) const CHAR *Input,
                                                                    _In_ DWORD                              InputLength,
                                                                    _Out_writes_bytes_(*OutputLength) CHAR *Output,
                                                                    _Inout_ DWORD       *OutputLength,
                                                                    _Out_ SOCKADDR_INET *ResolvedDemoServer) {
    SOCKET     Socket = INVALID_SOCKET;
    ADDRINFOW  Hints  = {};
    ADDRINFOW *Resolution;
    BOOL       Ret = FALSE;

    Hints.ai_family   = AF_UNSPEC;
    Hints.ai_socktype = SOCK_STREAM;
    Hints.ai_protocol = IPPROTO_TCP;

    if (GetAddrInfoW(L"demo.wireguard.com", L"42912", &Hints, &Resolution)) {
        return FALSE;
    }
    for (ADDRINFOW *Candidate = Resolution; Candidate; Candidate = Candidate->ai_next) {
        if (Candidate->ai_family != AF_INET && Candidate->ai_family != AF_INET6) {
            continue;
        }
        Socket = socket(Candidate->ai_family, Candidate->ai_socktype, Candidate->ai_protocol);
        if (Socket == INVALID_SOCKET) {
            goto cleanupResolution;
        }
        if (connect(Socket, Candidate->ai_addr, (int)Candidate->ai_addrlen) == SOCKET_ERROR) {
            closesocket(Socket);
            Socket = INVALID_SOCKET;
        }
        memcpy(ResolvedDemoServer, Candidate->ai_addr, Candidate->ai_addrlen);
        break;
    }
    if (Socket == INVALID_SOCKET) {
        goto cleanupResolution;
    }
    if (send(Socket, Input, InputLength, 0) == SOCKET_ERROR) {
        goto cleanupSocket;
    }
    if ((*OutputLength = recv(Socket, Output, *OutputLength, 0)) == SOCKET_ERROR) {
        goto cleanupSocket;
    }
    Ret = TRUE;
cleanupSocket:
    closesocket(Socket);
cleanupResolution:
    FreeAddrInfoW(Resolution);
    return Ret;
}

typedef struct {
    WIREGUARD_INTERFACE  Interface;
    WIREGUARD_PEER       RemoteServer;
    WIREGUARD_ALLOWED_IP AllV4;
    WIREGUARD_ALLOWED_IP AllV4_2;
} WG_CONFIG_INFO;

int main_wireguard_getinfo() {
    DWORD          Bytes;
    WG_CONFIG_INFO Config {};
    HMODULE        WireGuard = InitializeWireGuardNT();
    if (!WireGuard) {
        SPDLOG_ERROR("Failed to initialize WireGuardNT: {0}", GetLastError());
        return -1;
    }

    /* CryptStringToBinary(TEXT("WGAlqvys3O83VmWug6Z8NzUrxGr/PNHSeOVFnKLSe2k="),
                        0,
                        CRYPT_STRING_BASE64,
                        Config.Interface.PublicKey,
                        &Bytes,
                        nullptr,
                        nullptr);*/

    WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(L"admin");

    if (!Adapter) {
        SPDLOG_ERROR("Failed to create adapter: {0}", GetLastError());
        return -1;
    }

    Bytes = sizeof(Config);
    if (!WireGuardGetConfiguration(Adapter, &Config.Interface, &Bytes)) {
        SPDLOG_ERROR("Failed to get configuration: {0}", GetLastError());
        //WireGuardCloseAdapter(Adapter);
        return -3;
    }

    //WireGuardCloseAdapter(Adapter);

    return 0;
}

int main_wireguard(bool isDelete) {
    WG_CONFIG_INFO           Config {};
    DWORD                    LastError;
    WSADATA                  WsaData;
    MIB_IPINTERFACE_ROW      IpInterface = {};
    DWORD                    Bytes;
    MIB_UNICASTIPADDRESS_ROW AddressRow;
    GUID                     ExampleGuid = {
        0xdeadc001,
        0xbeef,
        0xbabe,
        {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
    };
    WIREGUARD_ADAPTER_HANDLE Adapter;
    DWORD                    Version;

    if (WSAStartup(MAKEWORD(2, 2), &WsaData)) {
        SPDLOG_ERROR("Failed to initialize Winsock: {0}", GetLastError());
        return -1;
    }
    HMODULE WireGuard = InitializeWireGuardNT();
    if (!WireGuard) {
        SPDLOG_ERROR("Failed to initialize WireGuardNT: {0}", GetLastError());
        return -2;
    }

    SPDLOG_INFO("WireGuardNT library loaded");

    // 配置本地隧道接口
    Config.Interface.Flags      = WIREGUARD_INTERFACE_HAS_PRIVATE_KEY;
    Config.Interface.PeersCount = 1;
    CryptStringToBinary(TEXT("WGAlqvys3O83VmWug6Z8NzUrxGr/PNHSeOVFnKLSe2k="),
                        0,
                        CRYPT_STRING_BASE64,
                        Config.Interface.PrivateKey,
                        &Bytes,
                        nullptr,
                        nullptr);

    // 配置远程隧道接口
    Config.RemoteServer.Flags = static_cast<WIREGUARD_PEER_FLAG>(
        WIREGUARD_PEER_HAS_PUBLIC_KEY | WIREGUARD_PEER_HAS_PERSISTENT_KEEPALIVE | WIREGUARD_PEER_HAS_ENDPOINT);
    Config.RemoteServer.AllowedIPsCount     = 2;
    Config.RemoteServer.PersistentKeepalive = 30;
    CryptStringToBinary(TEXT("q3ep8hN2v3VpHbcru+rTmvyBt13iH0DkEsVAyT2LpVo="),
                        0,
                        CRYPT_STRING_BASE64,
                        Config.RemoteServer.PublicKey,
                        &Bytes,
                        nullptr,
                        nullptr);

    if (!GetEndPoint(TEXT("efc.xajhuang.com:10000"), &Config.RemoteServer.Endpoint)) {
        SPDLOG_ERROR("Failed to talk to demo server: {0}", GetLastError());
        goto cleanupWireGuard;
    }

    Config.AllV4.AddressFamily = AF_INET;
    Config.AllV4.Cidr          = 24;
    InetPtonA(AF_INET, TEXT("10.10.10.0"), &Config.AllV4.Address.V4.S_un.S_addr);

    Config.AllV4_2.AddressFamily = AF_INET;
    Config.AllV4_2.Cidr          = 24;
    InetPtonA(AF_INET, TEXT("192.168.100.0"), &Config.AllV4.Address.V4.S_un.S_addr);

    InitializeUnicastIpAddressEntry(&AddressRow);
    AddressRow.Address.Ipv4.sin_family = AF_INET;
    AddressRow.OnLinkPrefixLength      = 24; /* This is a /24 network */
    AddressRow.DadState                = IpDadStatePreferred;
    InetPtonA(AF_INET, TEXT("10.10.10.1"), &AddressRow.Address.Ipv4.sin_addr);

#if 0
    Log(WIREGUARD_LOG_INFO, L"Generating keypair");
    if (!GenerateKeyPair(PublicKey, Config.Interface.PrivateKey)) {
        LastError = LogError(L"Failed to generate keypair", GetLastError());
        goto cleanupWireGuard;
    }

    Bytes = sizeof(PublicKeyString);
    CryptBinaryToStringA(PublicKey,
                         sizeof(PublicKey),
                         CRYPT_STRING_BASE64 | CRYPT_STRING_NOCR,
                         PublicKeyString,
                         &Bytes);

    Log(WIREGUARD_LOG_INFO, L"Talking to demo server");
    Bytes = sizeof(ServerResponse) - 1;
    if (!TalkToDemoServer(PublicKeyString,
                          (DWORD)strlen(PublicKeyString),
                          ServerResponse,
                          &Bytes,
                          &Config.RemoteServer.Endpoint)) {
        LastError = LogError(L"Failed to talk to demo server", GetLastError());
        goto cleanupWireGuard;
    }

    Colon1 = strchr(ServerResponse, ':');
    Colon2 = Colon1 ? strchr(Colon1 + 1, ':') : nullptr;
    Colon3 = Colon2 ? strchr(Colon2 + 1, ':') : nullptr;

    if (!Colon1 || !Colon2 || !Colon3) {
        LastError = LogError(L"Failed to parse demo server response", ERROR_UNDEFINED_CHARACTER);
        goto cleanupWireGuard;
    }
    if (Bytes && ServerResponse[--Bytes] == '\n') {
        ServerResponse[Bytes] = '\0';
    }
    *Colon1 = *Colon2 = *Colon3 = '\0';

    
    Bytes                              = sizeof(Config.RemoteServer.PublicKey);
    if (strcmp(ServerResponse, "OK") ||  != 1 ||
        !CryptStringToBinaryA(Colon1 + 1,
                              0,
                              CRYPT_STRING_BASE64,
                              Config.RemoteServer.PublicKey,
                              &Bytes,
                              nullptr,
                              nullptr)) {
        LastError = LogError(L"Failed to parse demo server response", ERROR_UNDEFINED_CHARACTER);
        goto cleanupWireGuard;
    }
    if (Config.RemoteServer.Endpoint.si_family == AF_INET) {
        Config.RemoteServer.Endpoint.Ipv4.sin_port = htons((u_short)atoi(Colon2 + 1));
    } else if (Config.RemoteServer.Endpoint.si_family == AF_INET6) {
        Config.RemoteServer.Endpoint.Ipv6.sin6_port = htons((u_short)atoi(Colon2 + 1));
    }
#endif

    QuitEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    if (!QuitEvent) {
        SPDLOG_ERROR("Failed to create event:{0}", GetLastError());
        goto cleanupWireGuard;
    }
    //if (!SetConsoleCtrlHandler(CtrlHandler, TRUE)) {
    //    SPDLOG_ERROR("Failed to set console handler: {0}", GetLastError());
    //    goto cleanupQuit;
    //}

    Adapter = WireGuardOpenAdapter(L"Demo");

    if (isDelete) {
        SPDLOG_INFO("Remove adapter: {0}", "Demo");
        WireGuardCloseAdapter(Adapter);
        return 0;
    }

    if (!Adapter) {
        Adapter = WireGuardCreateAdapter(L"Demo", L"Example", &ExampleGuid);
        if (!Adapter) {
            SPDLOG_ERROR("Failed to create adapter: {0}", LastError);
            goto cleanupQuit;
        }
    }

    if (!WireGuardSetAdapterLogging(Adapter, WIREGUARD_ADAPTER_LOG_ON)) {
        SPDLOG_ERROR("Failed to enable adapter logging: {0}", GetLastError());
    }

    Version = WireGuardGetRunningDriverVersion();
    SPDLOG_INFO("WireGuardNT v{0}.{1} loaded", (Version >> 16) & 0xff, (Version >> 0) & 0xff);

    WireGuardGetAdapterLUID(Adapter, &AddressRow.InterfaceLuid);

    //SetInterfaceIpAddress("Demo", "10.10.10.1", "255.255.255.0");

#if 0
    InitializeIpForwardEntry(&DefaultRoute);
    DefaultRoute.InterfaceLuid                      = AddressRow.InterfaceLuid;
    DefaultRoute.DestinationPrefix.Prefix.si_family = AF_INET;
    DefaultRoute.NextHop.si_family                  = AF_INET;
    DefaultRoute.Metric                             = 0;
    LastError                                       = CreateIpForwardEntry2(&DefaultRoute);
    if (LastError != ERROR_SUCCESS && LastError != ERROR_OBJECT_ALREADY_EXISTS) {
        SPDLOG_ERROR("Failed to set default route: {0}", LastError);
        goto cleanupAdapter;
    }
    LastError = CreateUnicastIpAddressEntry(&AddressRow);
    if (LastError != ERROR_SUCCESS && LastError != ERROR_OBJECT_ALREADY_EXISTS) {
        SPDLOG_ERROR("Failed to set IP address: {0}", LastError);
        goto cleanupAdapter;
    }
#endif
#if 1
    InitializeIpInterfaceEntry(&IpInterface);
    IpInterface.InterfaceLuid = AddressRow.InterfaceLuid;
    IpInterface.Family        = AF_INET;
    LastError                 = GetIpInterfaceEntry(&IpInterface);
    if (LastError != ERROR_SUCCESS) {
        SPDLOG_ERROR("Failed to get IP interface: {0}", LastError);
        goto cleanupAdapter;
    }

    /*if ((LastError = AddIpAddress(IpInterface.InterfaceIndex)) != 0) {
        SPDLOG_ERROR("Set interface IP Error: {0}", LastError);
        goto cleanupAdapter;
    }*/

    IpInterface.UseAutomaticMetric = FALSE;
    IpInterface.Metric             = 0;
    IpInterface.NlMtu              = 1420;
    IpInterface.SitePrefixLength   = 0;
    LastError                      = SetIpInterfaceEntry(&IpInterface);
    if (LastError != ERROR_SUCCESS) {
        SPDLOG_ERROR("Failed to set metric and MTU: {0}", LastError);
        goto cleanupAdapter;
    }
#endif

    SPDLOG_INFO("Setting configuration and adapter up");
    if (!WireGuardSetConfiguration(Adapter, &Config.Interface, sizeof(Config)) ||
        !WireGuardSetAdapterState(Adapter, WIREGUARD_ADAPTER_STATE_UP)) {
        SPDLOG_ERROR("Failed to set configuration and adapter up: {0}", GetLastError());
        goto cleanupAdapter;
    }

    do {
        Bytes = sizeof(Config);
        if (!WireGuardGetConfiguration(Adapter, &Config.Interface, &Bytes) || !Config.Interface.PeersCount) {
            SPDLOG_ERROR("Failed to get configuration: {0}", GetLastError());
            goto cleanupAdapter;
        }
        DWORD64    Timestamp = Now();
        SYSTEMTIME SystemTime;
        FileTimeToSystemTime((FILETIME *)&Timestamp, &SystemTime);
        fwprintf(stderr,
                 L"%04u-%02u-%02u %02u:%02u:%02u.%04u [#] RX: %llu, TX: %llu\r",
                 SystemTime.wYear,
                 SystemTime.wMonth,
                 SystemTime.wDay,
                 SystemTime.wHour,
                 SystemTime.wMinute,
                 SystemTime.wSecond,
                 SystemTime.wMilliseconds,
                 Config.RemoteServer.RxBytes,
                 Config.RemoteServer.TxBytes);
    } while (WaitForSingleObject(QuitEvent, 1000) == WAIT_TIMEOUT);

cleanupAdapter:
    WireGuardCloseAdapter(Adapter);
cleanupQuit:
    //SetConsoleCtrlHandler(CtrlHandler, FALSE);
    CloseHandle(QuitEvent);
cleanupWireGuard:
    FreeLibrary(WireGuard);
cleanupWinsock:
    WSACleanup();
    return 0;
}