#include <Arduino.h>
#include <unity.h>

#include "batch_reassembly_logic.h"
#include "lora_frame_logic.h"

static void test_crc16_known_vectors() {
  const uint8_t canonical[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
  TEST_ASSERT_EQUAL_HEX16(0x29B1, lora_crc16_ccitt(canonical, sizeof(canonical)));

  const uint8_t binary[] = {0x00, 0x01, 0x02, 0x03, 0x04};
  TEST_ASSERT_EQUAL_HEX16(0x1C0F, lora_crc16_ccitt(binary, sizeof(binary)));
}

static void test_frame_encode_decode_and_crc_reject() {
  const uint8_t payload[] = {0x01, 0x02, 0xA5};
  uint8_t frame[64] = {};
  size_t frame_len = 0;
  TEST_ASSERT_TRUE(lora_build_frame(0, 0xF19C, payload, sizeof(payload), frame, sizeof(frame), frame_len));
  TEST_ASSERT_EQUAL_UINT(8, frame_len);

  uint8_t out_kind = 0xFF;
  uint16_t out_device_id = 0;
  uint8_t out_payload[16] = {};
  size_t out_payload_len = 0;
  LoraFrameDecodeStatus ok = lora_parse_frame(frame, frame_len, 1, &out_kind, &out_device_id, out_payload,
                                              sizeof(out_payload), &out_payload_len);
  TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(LoraFrameDecodeStatus::Ok), static_cast<uint8_t>(ok));
  TEST_ASSERT_EQUAL_UINT8(0, out_kind);
  TEST_ASSERT_EQUAL_UINT16(0xF19C, out_device_id);
  TEST_ASSERT_EQUAL_UINT(sizeof(payload), out_payload_len);
  TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, out_payload, sizeof(payload));

  frame[frame_len - 1] ^= 0x01;
  LoraFrameDecodeStatus bad_crc = lora_parse_frame(frame, frame_len, 1, &out_kind, &out_device_id, out_payload,
                                                   sizeof(out_payload), &out_payload_len);
  TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(LoraFrameDecodeStatus::CrcFail), static_cast<uint8_t>(bad_crc));
}

static void test_frame_rejects_invalid_msg_kind_and_short_length() {
  const uint8_t payload[] = {0x42};
  uint8_t frame[32] = {};
  size_t frame_len = 0;
  TEST_ASSERT_TRUE(lora_build_frame(2, 0xF19C, payload, sizeof(payload), frame, sizeof(frame), frame_len));

  uint8_t out_kind = 0;
  uint16_t out_device_id = 0;
  uint8_t out_payload[8] = {};
  size_t out_payload_len = 0;
  LoraFrameDecodeStatus invalid_msg = lora_parse_frame(frame, frame_len, 1, &out_kind, &out_device_id, out_payload,
                                                       sizeof(out_payload), &out_payload_len);
  TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(LoraFrameDecodeStatus::InvalidMsgKind), static_cast<uint8_t>(invalid_msg));

  LoraFrameDecodeStatus short_len = lora_parse_frame(frame, 4, 1, &out_kind, &out_device_id, out_payload,
                                                     sizeof(out_payload), &out_payload_len);
  TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(LoraFrameDecodeStatus::LengthMismatch), static_cast<uint8_t>(short_len));
}

static void test_chunk_reassembly_in_order_success() {
  BatchReassemblyState state = {};
  batch_reassembly_reset(state);

  const uint8_t payload[] = {1, 2, 3, 4, 5, 6, 7};
  uint8_t buffer[32] = {};
  uint16_t complete_len = 0;

  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::InProgress),
      static_cast<uint8_t>(batch_reassembly_push(state, 77, 0, 3, 7, &payload[0], 3, 1000, 5000, 32, buffer, sizeof(buffer), complete_len)));
  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::InProgress),
      static_cast<uint8_t>(batch_reassembly_push(state, 77, 1, 3, 7, &payload[3], 2, 1100, 5000, 32, buffer, sizeof(buffer), complete_len)));
  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::Complete),
      static_cast<uint8_t>(batch_reassembly_push(state, 77, 2, 3, 7, &payload[5], 2, 1200, 5000, 32, buffer, sizeof(buffer), complete_len)));

  TEST_ASSERT_EQUAL_UINT16(7, complete_len);
  TEST_ASSERT_FALSE(state.active);
  TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, buffer, sizeof(payload));
}

static void test_chunk_reassembly_missing_or_out_of_order_fails_deterministically() {
  BatchReassemblyState state = {};
  batch_reassembly_reset(state);

  const uint8_t payload[] = {9, 8, 7, 6, 5, 4};
  uint8_t buffer[32] = {};
  uint16_t complete_len = 0;

  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::InProgress),
      static_cast<uint8_t>(batch_reassembly_push(state, 10, 0, 3, 6, &payload[0], 2, 1000, 5000, 32, buffer, sizeof(buffer), complete_len)));
  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::ErrorReset),
      static_cast<uint8_t>(batch_reassembly_push(state, 10, 2, 3, 6, &payload[4], 2, 1100, 5000, 32, buffer, sizeof(buffer), complete_len)));
  TEST_ASSERT_FALSE(state.active);

  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::ErrorReset),
      static_cast<uint8_t>(batch_reassembly_push(state, 11, 1, 3, 6, &payload[2], 2, 1200, 5000, 32, buffer, sizeof(buffer), complete_len)));
}

static void test_chunk_reassembly_wrong_total_length_fails() {
  BatchReassemblyState state = {};
  batch_reassembly_reset(state);

  const uint8_t payload[] = {1, 2, 3, 4, 5, 6};
  uint8_t buffer[8] = {};
  uint16_t complete_len = 0;

  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::InProgress),
      static_cast<uint8_t>(batch_reassembly_push(state, 55, 0, 2, 5, &payload[0], 3, 1000, 5000, 8, buffer, sizeof(buffer), complete_len)));
  TEST_ASSERT_EQUAL_UINT8(
      static_cast<uint8_t>(BatchReassemblyStatus::ErrorReset),
      static_cast<uint8_t>(batch_reassembly_push(state, 55, 1, 2, 5, &payload[3], 3, 1100, 5000, 8, buffer, sizeof(buffer), complete_len)));
  TEST_ASSERT_FALSE(state.active);
}

void setup() {
  UNITY_BEGIN();
  RUN_TEST(test_crc16_known_vectors);
  RUN_TEST(test_frame_encode_decode_and_crc_reject);
  RUN_TEST(test_frame_rejects_invalid_msg_kind_and_short_length);
  RUN_TEST(test_chunk_reassembly_in_order_success);
  RUN_TEST(test_chunk_reassembly_missing_or_out_of_order_fails_deterministically);
  RUN_TEST(test_chunk_reassembly_wrong_total_length_fails);
  UNITY_END();
}

void loop() {}
