#  Copyright (C) GridGain Systems. All Rights Reserved.
#  _________        _____ __________________        _____
#  __  ____/___________(_)______  /__  ____/______ ____(_)_______
#  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
#  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
#  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/

# Default duration (in seconds) for `run-fuzz-*` custom targets.
# Override at configure time, e.g. `cmake -DFUZZ_RUN_TIME=3600 ...`.
set(FUZZ_RUN_TIME 300 CACHE STRING
    "Wall-clock seconds each `run-fuzz-*` target runs before exiting (0 = until Ctrl-C)")

# Helper: create a libFuzzer harness executable.
# Each harness is linked with -fsanitize=fuzzer so it gets the fuzzer main().
# The -fsanitize=fuzzer-no-link flag is applied globally (in the root
# CMakeLists) so all compiled library code carries coverage instrumentation.
function(add_fuzz_target TARGET_NAME)
    cmake_parse_arguments(ARG "" "" "SOURCES;LIBS;INCLUDE_DIRS" ${ARGN})

    add_executable(${TARGET_NAME} ${ARG_SOURCES})
    target_link_libraries(${TARGET_NAME} PRIVATE ${ARG_LIBS})
    if (ARG_INCLUDE_DIRS)
        target_include_directories(${TARGET_NAME} PRIVATE ${ARG_INCLUDE_DIRS})
    endif()
    target_link_options(${TARGET_NAME} PRIVATE -fsanitize=fuzzer)
endfunction()

# Helper: register a `run-<harness>` custom target that invokes the binary
# with the canonical argument layout — work/<name>/ first (writable), then
# corpus/<name>/ (read-only seeds), plus the dict and time/length caps.
# This avoids hand-typing the args and ensures new finds always land in
# fuzz/work/, never in fuzz/corpus/.
function(add_fuzz_run_target HARNESS)
    cmake_parse_arguments(ARG "" "CORPUS_NAME;DICT;MAX_LEN" "" ${ARGN})

    set(FUZZ_ROOT   "${CMAKE_CURRENT_SOURCE_DIR}")
    set(WORK_DIR    "${FUZZ_ROOT}/work/${ARG_CORPUS_NAME}")
    set(CORPUS_DIR  "${FUZZ_ROOT}/corpus/${ARG_CORPUS_NAME}")

    set(RUN_ARGS
        "${WORK_DIR}"
        "${CORPUS_DIR}"
        "-max_len=${ARG_MAX_LEN}"
        "-max_total_time=${FUZZ_RUN_TIME}"
        "-print_final_stats=1"
    )
    if (ARG_DICT)
        list(APPEND RUN_ARGS "-dict=${FUZZ_ROOT}/dict/${ARG_DICT}")
    endif()

    add_custom_target(run-${HARNESS}
        # Make sure the writable corpus dir exists before libFuzzer is invoked.
        COMMAND ${CMAKE_COMMAND} -E make_directory "${WORK_DIR}"
        COMMAND $<TARGET_FILE:${HARNESS}> ${RUN_ARGS}
        DEPENDS ${HARNESS}
        # Run from fuzz/ so any crash-* / oom-* artefacts land where the
        # fuzz/.gitignore rules already exclude them.
        WORKING_DIRECTORY "${FUZZ_ROOT}"
        USES_TERMINAL
        VERBATIM
        COMMENT "Running ${HARNESS} (up to ${FUZZ_RUN_TIME}s; Ctrl-C to stop)"
    )
endfunction()

# ── ODBC parsing harnesses ────────────────────────────────────────────────────
# These depend on the ODBC module (gridgain9-odbc-obj) being built.
# Enable with: cmake -DENABLE_FUZZER=ON -DENABLE_ODBC=ON ...
if (ENABLE_ODBC)
    # Mirror the library list used by the ODBC unit tests (see ignite/odbc/CMakeLists.txt).
    set(ODBC_FUZZ_LIBS
        gridgain9-odbc-obj
        gridgain-common
        gridgain-tuple
        gridgain-network
        gridgain-protocol
        ${ODBC_LIBRARIES}
    )
    if (WIN32)
        list(APPEND ODBC_FUZZ_LIBS odbccp32 shlwapi)
    elseif(NOT APPLE)
        list(APPEND ODBC_FUZZ_LIBS odbcinst)
    endif()

    # Fuzz the semicolon-delimited connection string format used by SQLDriverConnect.
    add_fuzz_target(fuzz-parse-connection-string
        SOURCES fuzz_parse_connection_string.cpp
        LIBS    ${ODBC_FUZZ_LIBS}
    )
    add_fuzz_run_target(fuzz-parse-connection-string
        CORPUS_NAME parse_connection_string
        DICT        connection_string.dict
        MAX_LEN     4096
    )

    # Fuzz the null-delimited DSN attribute format passed by the ODBC driver manager.
    add_fuzz_target(fuzz-parse-config-attributes
        SOURCES fuzz_parse_config_attributes.cpp
        LIBS    ${ODBC_FUZZ_LIBS}
    )
    add_fuzz_run_target(fuzz-parse-config-attributes
        CORPUS_NAME parse_config_attributes
        DICT        connection_string.dict
        MAX_LEN     4096
    )

    # Fuzz the comma-separated host:port address list (SERVER= / ADDRESS= config key).
    add_fuzz_target(fuzz-parse-address
        SOURCES fuzz_parse_address.cpp
        LIBS    ${ODBC_FUZZ_LIBS}
    )
    add_fuzz_run_target(fuzz-parse-address
        CORPUS_NAME parse_address
        MAX_LEN     2048
    )
else()
    message(STATUS "Fuzz: skipping ODBC harnesses (ENABLE_ODBC is OFF)")
endif()

# ── TCP range harness ─────────────────────────────────────────────────────────
# tcp_range.cpp has no library dependencies beyond the C++ stdlib, so we compile
# it directly rather than pulling in the full gridgain-network OBJECT library.
add_fuzz_target(fuzz-tcp-range
    SOURCES
        fuzz_tcp_range.cpp
        ${IGNITE_CMAKE_TOP_DIR}/ignite/network/tcp_range.cpp
    INCLUDE_DIRS
        ${IGNITE_CMAKE_TOP_DIR}
        ${IGNITE_CMAKE_TOP_DIR}/ignite/network
)
add_fuzz_run_target(fuzz-tcp-range
    CORPUS_NAME tcp_range
    DICT        tcp_range.dict
    MAX_LEN     512
)

# ── Common-type string-parser harnesses ───────────────────────────────────────
# These exercise the `from_string` / string-constructor path on the public
# data types in ignite::common.  They depend only on gridgain-common (STATIC),
# which transitively brings in TF-PSA-Crypto (used by big_integer) and
# uni-algo, so no ODBC / network setup is required.

# Fuzz the arbitrary-precision integer parser.  Reaches into mbedtls' MPI
# string reader via TF-PSA-Crypto — crashes on either side are real bugs.
add_fuzz_target(fuzz-big-integer-from-string
    SOURCES fuzz_big_integer_from_string.cpp
    LIBS    gridgain-common
)
add_fuzz_run_target(fuzz-big-integer-from-string
    CORPUS_NAME big_integer_from_string
    DICT        numeric.dict
    MAX_LEN     1024
)

# Fuzz the arbitrary-precision decimal parser.  Exercises sign / decimal
# point / exponent handling unique to big_decimal, plus the magnitude path
# shared with big_integer.
add_fuzz_target(fuzz-big-decimal-from-string
    SOURCES fuzz_big_decimal_from_string.cpp
    LIBS    gridgain-common
)
add_fuzz_run_target(fuzz-big-decimal-from-string
    CORPUS_NAME big_decimal_from_string
    DICT        numeric.dict
    MAX_LEN     1024
)

# Fuzz the canonical 36-char textual UUID parser.  Returns std::optional<uuid>
# on malformed input.
add_fuzz_target(fuzz-uuid-from-string
    SOURCES fuzz_uuid_from_string.cpp
    LIBS    gridgain-common
)
add_fuzz_run_target(fuzz-uuid-from-string
    CORPUS_NAME uuid_from_string
    DICT        uuid.dict
    MAX_LEN     128
)
