/*
 *  Copyright (c) 2024, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>

#include <openthread/platform/dnssd.h>

#include "test_platform.h"
#include "test_util.h"
#include "common/code_utils.hpp"
#include "lib/spinel/spinel_buffer.hpp"
#include "lib/spinel/spinel_encoder.hpp"
#include "ncp/ncp_base.hpp"

#if OPENTHREAD_CONFIG_NCP_DNSSD_ENABLE && OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE

namespace ot {

constexpr uint16_t kMaxSpinelBufferSize = 2048;
static uint32_t    sRequestId;
static uint8_t     sError;

static otError GenerateSpinelDnssdSetStateFrame(otPlatDnssdState aState, uint8_t *aBuf, uint16_t &aLen)
{
    otError         error = OT_ERROR_NONE;
    uint8_t         buf[kMaxSpinelBufferSize];
    Spinel::Buffer  ncpBuffer(buf, kMaxSpinelBufferSize);
    Spinel::Encoder encoder(ncpBuffer);

    uint8_t header = SPINEL_HEADER_FLAG | 0 /* Iid */ | 1 /* Tid */;
    SuccessOrExit(error = encoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_DNSSD_STATE));
    SuccessOrExit(error = encoder.WriteUint8(aState));
    SuccessOrExit(error = encoder.EndFrame());

    SuccessOrExit(ncpBuffer.OutFrameBegin());
    aLen = ncpBuffer.OutFrameGetLength();
    VerifyOrExit(ncpBuffer.OutFrameRead(aLen, aBuf) == aLen, error = OT_ERROR_FAILED);

exit:
    return error;
}

static void TestPlatDnssdRegisterCallback(otInstance *aInstance, otPlatDnssdRequestId aRequestId, otError aError)
{
    sRequestId = aRequestId;
    sError     = aError;
}

static otError GenerateSpinelDnssdRequestResultFrame(uint32_t aRequestId, otError aError, uint8_t *aBuf, uint16_t &aLen)
{
    otError                     error = OT_ERROR_NONE;
    uint8_t                     buf[kMaxSpinelBufferSize];
    Spinel::Buffer              ncpBuffer(buf, kMaxSpinelBufferSize);
    Spinel::Encoder             encoder(ncpBuffer);
    otPlatDnssdRegisterCallback callback = &TestPlatDnssdRegisterCallback;

    uint8_t header = SPINEL_HEADER_FLAG | 0 /* Iid */ | 1 /* Tid */;
    SuccessOrExit(error = encoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_DNSSD_REQUEST_RESULT));
    SuccessOrExit(error = encoder.WriteUint8(aError));
    SuccessOrExit(error = encoder.WriteUint32(aRequestId));
    SuccessOrExit(error = encoder.WriteData(reinterpret_cast<const uint8_t *>(&callback), sizeof(callback)));
    SuccessOrExit(error = encoder.EndFrame());

    SuccessOrExit(ncpBuffer.OutFrameBegin());
    aLen = ncpBuffer.OutFrameGetLength();
    VerifyOrExit(ncpBuffer.OutFrameRead(aLen, aBuf) == aLen, error = OT_ERROR_FAILED);

exit:
    return error;
}

void TestNcpDnssdGetState(void)
{
    Instance    *instance = static_cast<Instance *>(testInitInstance());
    Ncp::NcpBase ncpBase(instance);

    uint8_t          recvBuf[kMaxSpinelBufferSize];
    uint16_t         recvLen;
    otPlatDnssdState dnssdState;

    dnssdState = otPlatDnssdGetState(instance);
    VerifyOrQuit(dnssdState == OT_PLAT_DNSSD_STOPPED);

    SuccessOrQuit(GenerateSpinelDnssdSetStateFrame(OT_PLAT_DNSSD_READY, recvBuf, recvLen));
    ncpBase.HandleReceive(recvBuf, recvLen);
    dnssdState = otPlatDnssdGetState(instance);
    VerifyOrQuit(dnssdState == OT_PLAT_DNSSD_READY);
}

void TestNcpDnssdRegistrations(void)
{
    Instance    *instance = static_cast<Instance *>(testInitInstance());
    Ncp::NcpBase ncpBase(instance);

    uint8_t            recvBuf[kMaxSpinelBufferSize];
    uint16_t           recvLen;
    otPlatDnssdHost    dnssdHost;
    otPlatDnssdService dnssdService;
    otPlatDnssdKey     dnssdKey;

    sRequestId = 0; // Use 0 to indicate `sRequestId` hasn't been set.

    // Register a dnssd host when the dnssd state is DISABLED
    dnssdHost.mHostName        = "ot-test";
    dnssdHost.mAddressesLength = 0;
    otPlatDnssdRegisterHost(instance, &dnssdHost, 1 /* aRequestId */, TestPlatDnssdRegisterCallback);
    VerifyOrQuit(sRequestId == 1);
    VerifyOrQuit(sError == OT_ERROR_INVALID_STATE);

    // Set the dnssd state to READY
    SuccessOrQuit(GenerateSpinelDnssdSetStateFrame(OT_PLAT_DNSSD_READY, recvBuf, recvLen));
    ncpBase.HandleReceive(recvBuf, recvLen);

    otPlatDnssdUnregisterHost(instance, &dnssdHost, 2 /* aRequestId */, TestPlatDnssdRegisterCallback);
    SuccessOrQuit(GenerateSpinelDnssdRequestResultFrame(2 /* aRequestId */, OT_ERROR_NOT_FOUND, recvBuf, recvLen));
    ncpBase.HandleReceive(recvBuf, recvLen);
    VerifyOrQuit(sRequestId == 2);
    VerifyOrQuit(sError == OT_ERROR_NOT_FOUND);

    dnssdService.mHostName            = "test-service";
    dnssdService.mServiceInstance     = nullptr;
    dnssdService.mServiceType         = nullptr;
    dnssdService.mSubTypeLabelsLength = 0;
    dnssdService.mPort                = 1234;
    dnssdService.mTxtDataLength       = 0;
    otPlatDnssdRegisterService(instance, &dnssdService, 3 /* aRequestId */, TestPlatDnssdRegisterCallback);
    SuccessOrQuit(GenerateSpinelDnssdRequestResultFrame(3 /* aRequestId */, OT_ERROR_NONE, recvBuf, recvLen));
    ncpBase.HandleReceive(recvBuf, recvLen);
    VerifyOrQuit(sRequestId == 3);
    VerifyOrQuit(sError == OT_ERROR_NONE);

    sRequestId = 0;
    sError     = OT_ERROR_FAILED;
    otPlatDnssdUnregisterService(instance, &dnssdService, 3 /* aRequestId */, TestPlatDnssdRegisterCallback);
    ncpBase.HandleReceive(recvBuf, recvLen);
    VerifyOrQuit(sRequestId == 3);
    VerifyOrQuit(sError == OT_ERROR_NONE);

    dnssdKey.mName          = "test-key";
    dnssdKey.mServiceType   = "someType";
    dnssdKey.mKeyDataLength = 0;
    otPlatDnssdRegisterKey(instance, &dnssdKey, 4 /* aRequestId */, TestPlatDnssdRegisterCallback);
    SuccessOrQuit(GenerateSpinelDnssdRequestResultFrame(4 /* aRequestId */, OT_ERROR_NONE, recvBuf, recvLen));
    ncpBase.HandleReceive(recvBuf, recvLen);
    VerifyOrQuit(sRequestId == 4);
    VerifyOrQuit(sError == OT_ERROR_NONE);

    sRequestId = 0;
    sError     = OT_ERROR_FAILED;
    otPlatDnssdUnregisterKey(instance, &dnssdKey, 4 /* aRequestId */, TestPlatDnssdRegisterCallback);
    ncpBase.HandleReceive(recvBuf, recvLen);
    VerifyOrQuit(sRequestId == 4);
    VerifyOrQuit(sError == OT_ERROR_NONE);
}

static bool sDnssdBrowseCallbackInvoked = false;

static void TestDnssdBrowseCallback(otInstance *aInstance, const otPlatDnssdBrowseResult *aResult)
{
    VerifyOrQuit(strcmp(aResult->mServiceType, "_ms._tcp") == 0);
    VerifyOrQuit(strcmp(aResult->mSubTypeLabel, "_battery") == 0);
    VerifyOrQuit(strcmp(aResult->mServiceInstance, "GAT-X105 #1") == 0);
    VerifyOrQuit(aResult->mTtl == 12345);
    VerifyOrQuit(aResult->mInfraIfIndex == 2);

    sDnssdBrowseCallbackInvoked = true;
}

static otError GenerateSpinelDnssdBrowseResultFrame(const otPlatDnssdBrowseResult &aBrowseResult,
                                                    uint8_t                       *aBuf,
                                                    uint16_t                      &aLen)
{
    otError                   error = OT_ERROR_NONE;
    uint8_t                   buf[kMaxSpinelBufferSize];
    Spinel::Buffer            ncpBuffer(buf, kMaxSpinelBufferSize);
    Spinel::Encoder           encoder(ncpBuffer);
    otPlatDnssdBrowseCallback callback = &TestDnssdBrowseCallback;

    uint8_t header = SPINEL_HEADER_FLAG | 0 /* Iid */ | 1 /* Tid */;
    SuccessOrExit(error = encoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_DNSSD_BROWSE_RESULT));
    SuccessOrExit(error = EncodeDnssdBrowseResult(encoder, aBrowseResult, reinterpret_cast<const uint8_t *>(&callback),
                                                  sizeof(callback)));
    SuccessOrExit(error = encoder.EndFrame());

    SuccessOrExit(ncpBuffer.OutFrameBegin());
    aLen = ncpBuffer.OutFrameGetLength();
    VerifyOrExit(ncpBuffer.OutFrameRead(aLen, aBuf) == aLen, error = OT_ERROR_FAILED);

exit:
    return error;
}

void TestNcpDnssdBrowse(void)
{
    Instance               *instance = static_cast<Instance *>(testInitInstance());
    Ncp::NcpBase            ncpBase(instance);
    uint8_t                 recvBuf[kMaxSpinelBufferSize];
    uint16_t                recvLen;
    otPlatDnssdBrowser      browser;
    otPlatDnssdBrowseResult browseResult;

    browser.mServiceType  = "_ms._tcp";
    browser.mSubTypeLabel = "_battery";
    browser.mInfraIfIndex = 2;
    browser.mCallback     = TestDnssdBrowseCallback;

    otPlatDnssdStartBrowser(instance, &browser);

    browseResult.mServiceType     = "_ms._tcp";
    browseResult.mSubTypeLabel    = "_battery";
    browseResult.mServiceInstance = "GAT-X105 #1";
    browseResult.mTtl             = 12345;
    browseResult.mInfraIfIndex    = 2;

    SuccessOrQuit(GenerateSpinelDnssdBrowseResultFrame(browseResult, recvBuf, recvLen));

    ncpBase.HandleReceive(recvBuf, recvLen);

    VerifyOrQuit(sDnssdBrowseCallbackInvoked);
}

static bool sDnssdSrvCallbackInvoked = false;

static void TestDnssdSrvCallback(otInstance *aInstance, const otPlatDnssdSrvResult *aResult)
{
    OT_UNUSED_VARIABLE(aInstance);

    VerifyOrQuit(strcmp(aResult->mServiceInstance, "GAT-X303 #1") == 0);
    VerifyOrQuit(strcmp(aResult->mServiceType, "_ms._tcp") == 0);
    VerifyOrQuit(strcmp(aResult->mHostName, "GAT-X303 #1._ms._tcp.local") == 0);
    VerifyOrQuit(aResult->mPort == 5353);
    VerifyOrQuit(aResult->mPriority == 1);
    VerifyOrQuit(aResult->mWeight == 10);
    VerifyOrQuit(aResult->mTtl == 120);
    VerifyOrQuit(aResult->mInfraIfIndex == 1);

    sDnssdSrvCallbackInvoked = true;
}

static otError GenerateSpinelDnssdSrvResultFrame(const otPlatDnssdSrvResult &aSrvResult, uint8_t *aBuf, uint16_t &aLen)
{
    otError                error = OT_ERROR_NONE;
    uint8_t                buf[kMaxSpinelBufferSize];
    Spinel::Buffer         ncpBuffer(buf, kMaxSpinelBufferSize);
    Spinel::Encoder        encoder(ncpBuffer);
    otPlatDnssdSrvCallback callback = &TestDnssdSrvCallback;

    uint8_t header = SPINEL_HEADER_FLAG | 0 /* Iid */ | 1 /* Tid */;
    SuccessOrExit(error = encoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_DNSSD_SRV_RESULT));
    SuccessOrExit(error = EncodeDnssdSrvResult(encoder, aSrvResult, reinterpret_cast<const uint8_t *>(&callback),
                                               sizeof(callback)));
    SuccessOrExit(error = encoder.EndFrame());

    SuccessOrExit(ncpBuffer.OutFrameBegin());
    aLen = ncpBuffer.OutFrameGetLength();
    VerifyOrExit(ncpBuffer.OutFrameRead(aLen, aBuf) == aLen, error = OT_ERROR_FAILED);

exit:
    return error;
}

void TestNcpDnssdSrvResolve(void)
{
    Instance              *instance = static_cast<Instance *>(testInitInstance());
    Ncp::NcpBase           ncpBase(instance);
    uint8_t                recvBuf[kMaxSpinelBufferSize];
    uint16_t               recvLen;
    otPlatDnssdSrvResolver resolver;
    otPlatDnssdSrvResult   srvResult;

    resolver.mServiceInstance = "GAT-X303 #1";
    resolver.mServiceType     = "_ms._tcp";
    resolver.mInfraIfIndex    = 1;
    resolver.mCallback        = TestDnssdSrvCallback;

    otPlatDnssdStartSrvResolver(instance, &resolver);

    srvResult.mServiceInstance = "GAT-X303 #1";
    srvResult.mServiceType     = "_ms._tcp";
    srvResult.mHostName        = "GAT-X303 #1._ms._tcp.local";
    srvResult.mPort            = 5353;
    srvResult.mPriority        = 1;
    srvResult.mWeight          = 10;
    srvResult.mTtl             = 120;
    srvResult.mInfraIfIndex    = 1;

    SuccessOrQuit(GenerateSpinelDnssdSrvResultFrame(srvResult, recvBuf, recvLen));

    ncpBase.HandleReceive(recvBuf, recvLen);

    VerifyOrQuit(sDnssdSrvCallbackInvoked);
}

} // namespace ot

#endif // OPENTHREAD_CONFIG_NCP_DNSSD_ENABLE && OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE

int main(void)
{
#if OPENTHREAD_CONFIG_NCP_DNSSD_ENABLE && OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE
    ot::TestNcpDnssdGetState();
    ot::TestNcpDnssdRegistrations();
    ot::TestNcpDnssdBrowse();
    ot::TestNcpDnssdSrvResolve();
#endif
    printf("All tests passed\n");
    return 0;
}
