# Copyright 2025-2026 mfaferek93, bburda
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.8)
project(ros2_medkit_fault_manager)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Shared cmake modules (multi-distro compat, ccache, linting)
find_package(ros2_medkit_cmake REQUIRED)
include(ROS2MedkitCompat)
include(ROS2MedkitCcache)
include(ROS2MedkitLinting)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion)
endif()

# Code coverage option
option(ENABLE_COVERAGE "Enable code coverage reporting" OFF)
if(ENABLE_COVERAGE)
  message(STATUS "Code coverage enabled")
  add_compile_options(--coverage -O0 -g)
  add_link_options(--coverage)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(ros2_medkit_msgs REQUIRED)
find_package(ros2_medkit_serialization REQUIRED)
find_package(SQLite3 REQUIRED)
find_package(nlohmann_json REQUIRED)
# yaml-cpp is required as transitive dependency from ros2_medkit_serialization
medkit_find_yaml_cpp()
# rosbag2 for time-window snapshot recording
find_package(rosbag2_cpp REQUIRED)
find_package(rosbag2_storage REQUIRED)

medkit_detect_compat_defs()

# Library target (shared between executable and tests)
add_library(fault_manager_lib STATIC
  src/fault_manager_node.cpp
  src/fault_storage.cpp
  src/sqlite_fault_storage.cpp
  src/snapshot_capture.cpp
  src/rosbag_capture.cpp
  src/correlation/types.cpp
  src/correlation/config_parser.cpp
  src/correlation/pattern_matcher.cpp
  src/correlation/correlation_engine.cpp
  src/entity_threshold_resolver.cpp
)

target_include_directories(fault_manager_lib PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>
)

medkit_target_dependencies(fault_manager_lib PUBLIC
  rclcpp
  ros2_medkit_msgs
  ros2_medkit_serialization
  rosbag2_cpp
  rosbag2_storage
)

target_link_libraries(fault_manager_lib PUBLIC
  SQLite::SQLite3
  nlohmann_json::nlohmann_json
  yaml-cpp::yaml-cpp
)

medkit_apply_compat_defs(fault_manager_lib)

if(ENABLE_COVERAGE)
  target_compile_options(fault_manager_lib PRIVATE --coverage -O0 -g)
  target_link_options(fault_manager_lib PRIVATE --coverage)
endif()

# Executable target
add_executable(fault_manager_node src/main.cpp)
target_link_libraries(fault_manager_node fault_manager_lib)

# Apply coverage flags to executable
if(ENABLE_COVERAGE)
  target_compile_options(fault_manager_node PRIVATE --coverage -O0 -g)
  target_link_options(fault_manager_node PRIVATE --coverage)
endif()

# Install targets
install(TARGETS fault_manager_node
  DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY include/
  DESTINATION include
)

install(DIRECTORY launch config
  DESTINATION share/${PROJECT_NAME}
)

# Testing
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  find_package(ament_cmake_gtest REQUIRED)
  find_package(launch_testing_ament_cmake REQUIRED)

  set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format")
  list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint ament_cmake_clang_tidy)
  ament_lint_auto_find_test_dependencies()

  ros2_medkit_clang_tidy()

  # Unit tests
  # Each GTest target that instantiates ROS 2 nodes gets a dedicated ROS_DOMAIN_ID
  # so it cannot interfere with launch_testing integration tests that run concurrently
  # in the same CTest invocation and launch their own fault_manager_node subprocess.
  ament_add_gtest(test_fault_manager test/test_fault_manager.cpp)
  target_link_libraries(test_fault_manager fault_manager_lib)
  medkit_target_dependencies(test_fault_manager rclcpp ros2_medkit_msgs)
  set_tests_properties(test_fault_manager PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=62")

  # SQLite storage tests
  ament_add_gtest(test_sqlite_storage test/test_sqlite_storage.cpp)
  target_link_libraries(test_sqlite_storage fault_manager_lib)
  medkit_target_dependencies(test_sqlite_storage rclcpp ros2_medkit_msgs)
  set_tests_properties(test_sqlite_storage PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=63")

  # Snapshot capture tests
  ament_add_gtest(test_snapshot_capture test/test_snapshot_capture.cpp)
  target_link_libraries(test_snapshot_capture fault_manager_lib)
  medkit_target_dependencies(test_snapshot_capture rclcpp ros2_medkit_msgs)
  set_tests_properties(test_snapshot_capture PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=64")

  # Rosbag capture tests
  ament_add_gtest(test_rosbag_capture test/test_rosbag_capture.cpp)
  target_link_libraries(test_rosbag_capture fault_manager_lib)
  medkit_target_dependencies(test_rosbag_capture rclcpp ros2_medkit_msgs)
  set_tests_properties(test_rosbag_capture PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=65")

  # Correlation config parser tests
  ament_add_gtest(test_correlation_config_parser test/test_correlation_config_parser.cpp)
  target_link_libraries(test_correlation_config_parser fault_manager_lib)

  # Pattern matcher tests
  ament_add_gtest(test_pattern_matcher test/test_pattern_matcher.cpp)
  target_link_libraries(test_pattern_matcher fault_manager_lib)

  # Correlation engine tests
  ament_add_gtest(test_correlation_engine test/test_correlation_engine.cpp)
  target_link_libraries(test_correlation_engine fault_manager_lib)

  # Entity threshold resolver tests
  ament_add_gtest(test_entity_thresholds test/test_entity_thresholds.cpp)
  target_link_libraries(test_entity_thresholds fault_manager_lib)
  medkit_target_dependencies(test_entity_thresholds rclcpp ros2_medkit_msgs)
  set_tests_properties(test_entity_thresholds PROPERTIES ENVIRONMENT "ROS_DOMAIN_ID=67")

  # Apply coverage flags to test targets
  if(ENABLE_COVERAGE)
    target_compile_options(test_fault_manager PRIVATE --coverage -O0 -g)
    target_link_options(test_fault_manager PRIVATE --coverage)
    target_compile_options(test_sqlite_storage PRIVATE --coverage -O0 -g)
    target_link_options(test_sqlite_storage PRIVATE --coverage)
    target_compile_options(test_snapshot_capture PRIVATE --coverage -O0 -g)
    target_link_options(test_snapshot_capture PRIVATE --coverage)
    target_compile_options(test_rosbag_capture PRIVATE --coverage -O0 -g)
    target_link_options(test_rosbag_capture PRIVATE --coverage)
    target_compile_options(test_correlation_config_parser PRIVATE --coverage -O0 -g)
    target_link_options(test_correlation_config_parser PRIVATE --coverage)
    target_compile_options(test_pattern_matcher PRIVATE --coverage -O0 -g)
    target_link_options(test_pattern_matcher PRIVATE --coverage)
    target_compile_options(test_correlation_engine PRIVATE --coverage -O0 -g)
    target_link_options(test_correlation_engine PRIVATE --coverage)
    target_compile_options(test_entity_thresholds PRIVATE --coverage -O0 -g)
    target_link_options(test_entity_thresholds PRIVATE --coverage)
  endif()

  # Integration tests
  install(DIRECTORY test
    DESTINATION share/${PROJECT_NAME}
  )

  add_launch_test(
    test/test_integration.test.py
    TARGET test_integration
    TIMEOUT 60
  )
  set_tests_properties(test_integration PROPERTIES LABELS "integration")

  add_launch_test(
    test/test_rosbag_integration.test.py
    TARGET test_rosbag_integration
    TIMEOUT 90
  )
  set_tests_properties(test_rosbag_integration PROPERTIES LABELS "integration")

  add_launch_test(
    test/test_entity_thresholds_integration.test.py
    TARGET test_entity_thresholds_integration
    TIMEOUT 60
  )
  set_tests_properties(test_entity_thresholds_integration PROPERTIES LABELS "integration")
endif()

ament_package()
