find_package(Unity REQUIRED)
find_package(CMock REQUIRED)

set(MOCK_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/mocks")
file(MAKE_DIRECTORY "${MOCK_GEN_DIR}")

# Generate a CMock mock from a header and attach it to a target.
# Usage: cmock_generate_mock(<target> <absolute-path-to-header>)
# CMock names generated files Mock<Name>.h/.c (capital M, no separator)
function(cmock_generate_mock target header)
    if (NOT RUBY_EXECUTABLE)
        message(FATAL_ERROR "Ruby is required for CMock generation")
    endif()
    get_filename_component(name "${header}" NAME_WE)
    get_filename_component(header_dir "${header}" DIRECTORY)
    set(mock_src "${MOCK_GEN_DIR}/Mock${name}.c")
    set(mock_hdr "${MOCK_GEN_DIR}/Mock${name}.h")
    add_custom_command(
        OUTPUT  "${mock_src}" "${mock_hdr}"
        COMMAND "${RUBY_EXECUTABLE}" "${CMOCK_SCRIPT}"
                "-o" "${CMAKE_CURRENT_SOURCE_DIR}/cmock.yml"
                "--mock_path=${MOCK_GEN_DIR}"
                "${header}"
        DEPENDS "${header}"
        COMMENT "CMock: generating Mock${name}"
        VERBATIM
    )
    target_sources("${target}" PRIVATE "${mock_src}")
    target_include_directories("${target}" PRIVATE "${MOCK_GEN_DIR}" "${header_dir}")
endfunction()

set(TEST_TARGETS "")

# CRSF tests — pure functions (CRC, parse, build), no mock needed
add_executable(test_crsf test_crsf.c)
target_include_directories(test_crsf PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_crsf PRIVATE celcrsf Unity::Unity)
target_compile_features(test_crsf PRIVATE c_std_23)
add_test(NAME test_crsf COMMAND test_crsf)
list(APPEND TEST_TARGETS test_crsf)

# CRSF stream tests
add_executable(test_crsf_stream test_crsf_stream.c)
target_include_directories(test_crsf_stream PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_crsf_stream PRIVATE celcrsf Unity::Unity)
target_compile_features(test_crsf_stream PRIVATE c_std_23)
add_test(NAME test_crsf_stream COMMAND test_crsf_stream)
list(APPEND TEST_TARGETS test_crsf_stream)

# CRSF telemetry tests
add_executable(test_crsf_telemetry test_crsf_telemetry.c)
target_include_directories(test_crsf_telemetry PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_crsf_telemetry PRIVATE celcrsf Unity::Unity)
target_compile_features(test_crsf_telemetry PRIVATE c_std_23)
add_test(NAME test_crsf_telemetry COMMAND test_crsf_telemetry)
list(APPEND TEST_TARGETS test_crsf_telemetry)

# CRSF param tests — mocks serial for write/ping/read
add_executable(test_crsf_param test_crsf_param.c)
target_include_directories(test_crsf_param PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_crsf_param PRIVATE celcrsf celserial Unity::Unity CMock::CMock)
target_compile_features(test_crsf_param PRIVATE c_std_23)
cmock_generate_mock(test_crsf_param "${CMAKE_SOURCE_DIR}/celrs/platform/serial_internal.h")
add_test(NAME test_crsf_param COMMAND test_crsf_param)
list(APPEND TEST_TARGETS test_crsf_param)

# Serial tests — mocks the platform backend (serial_internal.h)
add_executable(test_serial test_serial.c)
target_include_directories(test_serial PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_serial PRIVATE celserial Unity::Unity CMock::CMock)
target_compile_features(test_serial PRIVATE c_std_23)
cmock_generate_mock(test_serial "${CMAKE_SOURCE_DIR}/celrs/platform/serial_internal.h")
add_test(NAME test_serial COMMAND test_serial)
list(APPEND TEST_TARGETS test_serial)

# Logger tests — mocks log_write.h so output calls are intercepted
add_executable(test_logger test_logger.c)
target_include_directories(test_logger PRIVATE "${CMAKE_SOURCE_DIR}")
target_link_libraries(test_logger PRIVATE cellogger Unity::Unity CMock::CMock)
target_compile_features(test_logger PRIVATE c_std_23)
cmock_generate_mock(test_logger "${CMAKE_SOURCE_DIR}/celrs/log_write.h")
add_test(NAME test_logger COMMAND test_logger)
list(APPEND TEST_TARGETS test_logger)

# 'check' builds all suites and runs CTest with full Unity output.
# USES_TERMINAL keeps ANSI colors alive through Ninja's output buffering.
add_custom_target(check
    COMMAND ${CMAKE_CTEST_COMMAND}
            --test-dir "${CMAKE_BINARY_DIR}"
            --output-on-failure
            --progress
    WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
    USES_TERMINAL
    DEPENDS ${TEST_TARGETS}
)

if (ENABLE_COVERAGE)
    set(COVERAGE_DIR "${CMAKE_BINARY_DIR}/coverage")
    add_custom_target(coverage
        COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
        COMMAND ${CMAKE_COMMAND} -E make_directory "${COVERAGE_DIR}"
        COMMAND ${GCOVR_EXE}
                --gcov-executable "${GCOV_EXECUTABLE}"
                --root "${CMAKE_SOURCE_DIR}"
                --filter "${CMAKE_SOURCE_DIR}/celrs/"
                --exclude "${CMAKE_SOURCE_DIR}/tests/"
                --exclude ".*Mock.*"
                --exclude ".*unity.*"
                --exclude ".*cmock.*"
                --html-details "${COVERAGE_DIR}/index.html"
                --txt
                --print-summary
                "${CMAKE_BINARY_DIR}"
        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
        USES_TERMINAL
        DEPENDS ${TEST_TARGETS}
        COMMENT "Coverage report: ${COVERAGE_DIR}/index.html"
    )
endif()
