// HttpDnsBridge.cpp — NAPI 模块，把 Unity C#（经 P/Invoke）桥接到 HarmonyOS HTTPDNS
// ArkTS SDK（经 napi_threadsafe_function）。
//
// SDK 初始化不在此处——由 ArkTS 层在 setupHttpDnsBridge() 内于 Ability.onCreate（Unity 启动前）
// 完成。本模块只提供解析桥接。
//
// 线程模型：
//   - HttpDns_Resolve 由 Unity 的 ThreadPool（后台线程）调用。
//   - napi_threadsafe_function 把工作投递到 ArkTS 的 JS 事件循环线程。
//   - 信号量桥接两者：后台线程阻塞等待，JS 线程完成后 signal。
//
// 内存：返回的字符串均用 strdup()，调用方经 HttpDns_Free() 释放。

#include <napi/native_api.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <time.h>
#include <hilog/log.h>

#undef LOG_TAG
#define LOG_TAG "HTTPDNS-CPP"
#define LOG_DOMAIN 0x0000

// ===================================================================
// Pending request management
// ===================================================================

typedef struct {
    int id;
    sem_t sem;
    char *result;
} PendingRequest;

#define MAX_PENDING 64

static PendingRequest g_pending[MAX_PENDING];
static int g_nextId = 1;
static pthread_mutex_t g_pendingMutex = PTHREAD_MUTEX_INITIALIZER;

static PendingRequest *AllocRequest(void) {
    pthread_mutex_lock(&g_pendingMutex);
    for (int i = 0; i < MAX_PENDING; i++) {
        if (g_pending[i].id == 0) {
            g_pending[i].id = g_nextId++;
            if (g_nextId <= 0) g_nextId = 1;
            sem_init(&g_pending[i].sem, 0, 0);
            g_pending[i].result = NULL;
            pthread_mutex_unlock(&g_pendingMutex);
            return &g_pending[i];
        }
    }
    pthread_mutex_unlock(&g_pendingMutex);
    return NULL;
}

// Caller must hold g_pendingMutex — the timeout path frees slots concurrently.
static PendingRequest *FindRequest(int id) {
    for (int i = 0; i < MAX_PENDING; i++) {
        if (g_pending[i].id == id)
            return &g_pending[i];
    }
    return NULL;
}

static void FreeRequest(PendingRequest *req) {
    pthread_mutex_lock(&g_pendingMutex);
    sem_destroy(&req->sem);
    req->id = 0;
    // If the ArkTS result arrived after the waiter timed out, nobody took ownership
    // of the strdup'd string — free it here instead of leaking it.
    if (req->result != NULL) {
        free(req->result);
        req->result = NULL;
    }
    pthread_mutex_unlock(&g_pendingMutex);
}

// ===================================================================
// Resolve bridge
// ===================================================================

// Set once during ArkTS setup (registerResolveCallback), then read-only from
// background threads. The setup happens in Ability.onCreate before Unity starts,
// so no race.
static napi_threadsafe_function g_resolveTsfn = NULL;

// Real HTTPDNS availability — reported by ArkTS via setAvailability() and reflecting
// HttpDnsClient.isAvailable() (valid sdk_httpdns_config + Tencent SDK initConfig
// succeeded). This is NOT the same as "bridge registered": registerResolveCallback
// runs unconditionally, so g_resolveTsfn != NULL only means the bridge is wired —
// it must not be used as a proxy for "HTTPDNS usable". Written from the JS thread,
// read from Unity background threads; volatile for cross-thread visibility.
static volatile int g_available = 0;

typedef struct {
    int requestId;
    char domain[256];
} ResolveCallData;

// Runs on the JS thread when g_resolveTsfn is called.
// Invokes the ArkTS resolve callback with (domain, requestId).
static void CallJsResolve(napi_env env, napi_value jsCallback, void *context, void *data) {
    ResolveCallData *callData = (ResolveCallData *)data;

    napi_value argv[2];
    napi_create_string_utf8(env, callData->domain, NAPI_AUTO_LENGTH, &argv[0]);
    napi_create_int32(env, callData->requestId, &argv[1]);

    napi_value undefined;
    napi_get_undefined(env, &undefined);
    napi_call_function(env, undefined, jsCallback, 2, argv, NULL);

    free(callData);
}

// ===================================================================
// NAPI methods — called by ArkTS to set up the bridge
// ===================================================================

// ArkTS calls this to register its resolve callback.
// bridge.registerResolveCallback((domain, requestId) => { ... })
static napi_value RegisterResolveCallback(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];
    napi_get_cb_info(env, info, &argc, argv, NULL, NULL);

    napi_value resourceName;
    napi_create_string_utf8(env, "httpdns_resolve", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_threadsafe_function(env, argv[0], NULL, resourceName,
                                    0, 1, NULL, NULL, NULL, CallJsResolve, &g_resolveTsfn);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG, "registerResolveCallback: resolve bridge wired");
    return NULL;
}

// ArkTS calls this to report the real HTTPDNS availability — whether the Tencent SDK
// actually initialized (valid config). bridge.setAvailability(true/false)
static napi_value SetAvailability(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1];
    napi_get_cb_info(env, info, &argc, argv, NULL, NULL);

    bool available = false;
    napi_get_value_bool(env, argv[0], &available);
    g_available = available ? 1 : 0;
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, LOG_TAG,
        "setAvailability: HTTPDNS available=%{public}d", g_available);
    return NULL;
}

// ArkTS calls this when the HTTPDNS SDK Promise resolves.
// bridge.onResolveComplete(requestId, "ipv4|ipv6")
static napi_value OnResolveComplete(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value argv[2];
    napi_get_cb_info(env, info, &argc, argv, NULL, NULL);

    int requestId;
    napi_get_value_int32(env, argv[0], &requestId);

    char resultBuf[512];
    size_t len;
    napi_get_value_string_utf8(env, argv[1], resultBuf, sizeof(resultBuf), &len);

    // The mutex makes find+write+post atomic with respect to the waiter's timeout
    // path (FreeRequest). Without it: FindRequest matches, the waiter times out and
    // destroys the semaphore, then sem_post hits a destroyed semaphore — UB. With it,
    // either we complete first (post happens before any destroy) or the slot is
    // already freed (id == 0) and FindRequest misses — no post, no allocation.
    pthread_mutex_lock(&g_pendingMutex);
    PendingRequest *req = FindRequest(requestId);
    if (req) {
        req->result = (len > 0) ? strdup(resultBuf) : NULL;
        sem_post(&req->sem);
    }
    pthread_mutex_unlock(&g_pendingMutex);

    return NULL;
}

// ===================================================================
// NAPI module registration
// ===================================================================

static napi_value ModuleInit(napi_env env, napi_value exports) {
    napi_property_descriptor props[] = {
        {"registerResolveCallback", NULL, RegisterResolveCallback, NULL, NULL, NULL, napi_default, NULL},
        {"onResolveComplete",       NULL, OnResolveComplete,       NULL, NULL, NULL, napi_default, NULL},
        {"setAvailability",         NULL, SetAvailability,         NULL, NULL, NULL, napi_default, NULL},
    };
    napi_define_properties(env, exports, sizeof(props) / sizeof(props[0]), props);
    return exports;
}

static napi_module g_module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = NULL,
    .nm_register_func = ModuleInit,
    .nm_modname = "httpdns_bridge",
    .nm_priv = NULL,
    .reserved = {0},
};

__attribute__((constructor))
static void RegisterModule(void) {
    napi_module_register(&g_module);
}

// ===================================================================
// Exported C functions — called by Unity via P/Invoke
// ===================================================================

#ifdef __cplusplus
extern "C" {
#endif

// Blocks until ArkTS resolve callback posts the result via OnResolveComplete.
// Returns a strdup'd string "ipv4|ipv6", or NULL on failure.
// Caller must free via HttpDns_Free().
const char *HttpDns_Resolve(const char *domain) {
    if (!g_resolveTsfn || !domain) {
        OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, LOG_TAG,
            "HttpDns_Resolve early-out: bridge not registered (g_resolveTsfn=%{public}p) or domain empty", g_resolveTsfn);
        return NULL;
    }

    PendingRequest *req = AllocRequest();
    if (!req)
        return NULL;

    ResolveCallData *callData = (ResolveCallData *)malloc(sizeof(ResolveCallData));
    callData->requestId = req->id;
    strncpy(callData->domain, domain, sizeof(callData->domain) - 1);
    callData->domain[sizeof(callData->domain) - 1] = '\0';

    // If the post fails (e.g. environment tearing down), fail fast instead of
    // burning the 5s timeout — and don't leak the call data or the slot.
    if (napi_call_threadsafe_function(g_resolveTsfn, callData, napi_tsfn_blocking) != napi_ok) {
        free(callData);
        FreeRequest(req);
        return NULL;
    }

    // Block until ArkTS calls onResolveComplete for this request.
    // Timeout after 5s to prevent permanent hangs if the SDK never responds.
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += 5;
    int waitResult = sem_timedwait(&req->sem, &ts);

    char *result = NULL;
    if (waitResult == 0) {
        result = req->result;
        req->result = NULL;
    } else {
        OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, LOG_TAG,
            "HttpDns_Resolve timeout (5s): domain=%{public}s no result from ArkTS, returning NULL", domain);
    }

    FreeRequest(req);
    return result;
}

void HttpDns_Free(const char *ptr) {
    free((void *)ptr);
}

// HTTPDNS 是否可用：返回 ArkTS 经 setAvailability() 上报的真实初始化状态
// （凭据齐全且腾讯 SDK initConfig 成功），而非"桥是否注册"。返回 1=可用，0=不可用。
int HttpDns_IsAvailable(void) {
    return g_available;
}

#ifdef __cplusplus
}
#endif
