#include <Arduino.h>
#include <unity.h>
#include "dd3_legacy_core.h"
#include "html_util.h"

static void test_html_escape_basic() {
  TEST_ASSERT_EQUAL_STRING("", html_escape("").c_str());
  TEST_ASSERT_EQUAL_STRING("plain", html_escape("plain").c_str());
  TEST_ASSERT_EQUAL_STRING("a&amp;b", html_escape("a&b").c_str());
  TEST_ASSERT_EQUAL_STRING("&lt;tag&gt;", html_escape("<tag>").c_str());
  TEST_ASSERT_EQUAL_STRING("&quot;hi&quot;", html_escape("\"hi\"").c_str());
  TEST_ASSERT_EQUAL_STRING("it&#39;s", html_escape("it's").c_str());
  TEST_ASSERT_EQUAL_STRING("&amp;&lt;&gt;&quot;&#39;", html_escape("&<>\"'").c_str());
}

static void test_html_escape_adversarial() {
  TEST_ASSERT_EQUAL_STRING("&amp;amp;", html_escape("&amp;").c_str());
  TEST_ASSERT_EQUAL_STRING("\n\r\t", html_escape("\n\r\t").c_str());

  const String chunk = "<&>\"'abc\n\r\t";
  const String escaped_chunk = "&lt;&amp;&gt;&quot;&#39;abc\n\r\t";
  const size_t repeats = 300; // 3.3 KB input
  String input;
  String expected;
  input.reserve(chunk.length() * repeats);
  expected.reserve(escaped_chunk.length() * repeats);
  for (size_t i = 0; i < repeats; ++i) {
    input += chunk;
    expected += escaped_chunk;
  }

  String out = html_escape(input);
  TEST_ASSERT_EQUAL_UINT(expected.length(), out.length());
  TEST_ASSERT_EQUAL_STRING(expected.c_str(), out.c_str());
  TEST_ASSERT_TRUE(out.indexOf("&lt;&amp;&gt;&quot;&#39;abc") >= 0);
}

static void test_url_encode_component_table() {
  struct Case {
    const char *input;
    const char *expected;
  };

  const Case cases[] = {
      {"", ""},
      {"abcABC012-_.~", "abcABC012-_.~"},
      {"a b", "a%20b"},
      {"/\\?&#%\"'", "%2F%5C%3F%26%23%25%22%27"},
      {"line\nbreak", "line%0Abreak"},
  };

  for (size_t i = 0; i < (sizeof(cases) / sizeof(cases[0])); ++i) {
    String out = url_encode_component(cases[i].input);
    TEST_ASSERT_EQUAL_STRING(cases[i].expected, out.c_str());
  }

  String control;
  control += static_cast<char>(0x01);
  control += static_cast<char>(0x1F);
  control += static_cast<char>(0x7F);
  TEST_ASSERT_EQUAL_STRING("%01%1F%7F", url_encode_component(control).c_str());

  const String long_chunk = "AZaz09-_.~ /%?";
  const String long_expected_chunk = "AZaz09-_.~%20%2F%25%3F";
  String long_input;
  String long_expected;
  for (size_t i = 0; i < 40; ++i) { // 520 chars
    long_input += long_chunk;
    long_expected += long_expected_chunk;
  }
  String long_out_1 = url_encode_component(long_input);
  String long_out_2 = url_encode_component(long_input);
  TEST_ASSERT_EQUAL_STRING(long_expected.c_str(), long_out_1.c_str());
  TEST_ASSERT_EQUAL_STRING(long_out_1.c_str(), long_out_2.c_str());
}

static void test_sanitize_device_id_accepts_and_normalizes() {
  String out;
  const char *accept_cases[] = {
      "F19C",
      "f19c",
      "  f19c  ",
      "dd3-f19c",
      "dd3-F19C",
      "dd3-a0b1",
  };

  for (size_t i = 0; i < (sizeof(accept_cases) / sizeof(accept_cases[0])); ++i) {
    TEST_ASSERT_TRUE(sanitize_device_id(accept_cases[i], out));
    if (String(accept_cases[i]).indexOf("a0b1") >= 0) {
      TEST_ASSERT_EQUAL_STRING("dd3-A0B1", out.c_str());
    } else {
      TEST_ASSERT_EQUAL_STRING("dd3-F19C", out.c_str());
    }
  }
}

static void test_sanitize_device_id_rejects_invalid() {
  String out = "dd3-KEEP";
  const char *reject_cases[] = {
      "",
      "F",
      "FFF",
      "FFFFF",
      "dd3-12",
      "dd3-12345",
      "F1 9C",
      "dd3-F1\t9C",
      "dd3-F19C%00",
      "%F19C",
      "../F19C",
      "dd3-..1A",
      "dd3-12/3",
      "dd3-12\\3",
      "F19G",
      "dd3-zzzz",
  };
  for (size_t i = 0; i < (sizeof(reject_cases) / sizeof(reject_cases[0])); ++i) {
    TEST_ASSERT_FALSE(sanitize_device_id(reject_cases[i], out));
  }
  TEST_ASSERT_EQUAL_STRING("dd3-KEEP", out.c_str());
}

void setup() {
  dd3_legacy_core_force_link();
  UNITY_BEGIN();
  RUN_TEST(test_html_escape_basic);
  RUN_TEST(test_html_escape_adversarial);
  RUN_TEST(test_url_encode_component_table);
  RUN_TEST(test_sanitize_device_id_accepts_and_normalizes);
  RUN_TEST(test_sanitize_device_id_rejects_invalid);
  UNITY_END();
}

void loop() {}
