defmodule Aprsme.RegexCacheTest do
  # async: false — the cache is a singleton GenServer owning a named ETS table
  # shared across the whole application. Parallel tests would see each other.
  use ExUnit.Case, async: false

  alias Aprsme.RegexCache

  # Each test gets a unique pattern to avoid cross-test pollution of the
  # shared cache (we can't safely clear it without racing other callers).
  defp unique_pattern(tag) do
    suffix = System.unique_integer([:positive, :monotonic])
    "#{tag}_#{suffix}"
  end

  describe "get_or_compile/1" do
    test "compiles and returns a Regex on first call" do
      pattern = unique_pattern("hello")
      assert {:ok, regex} = RegexCache.get_or_compile(pattern)
      assert %Regex{} = regex
      assert Regex.match?(regex, pattern)
    end

    test "returns the same compiled regex on subsequent calls" do
      pattern = unique_pattern("same")
      assert {:ok, r1} = RegexCache.get_or_compile(pattern)
      assert {:ok, r2} = RegexCache.get_or_compile(pattern)
      assert r1 == r2
    end

    test "returns {:error, _} for invalid patterns" do
      assert {:error, _reason} = RegexCache.get_or_compile("[")
      assert {:error, _reason} = RegexCache.get_or_compile("(unbalanced")
    end

    test "does not cache failed compilations" do
      assert {:error, _} = RegexCache.get_or_compile("[bad")
      # Still an error on retry — error path didn't poison the cache with nil.
      assert {:error, _} = RegexCache.get_or_compile("[bad")
    end

    test "distinct patterns produce distinct regexes" do
      p1 = unique_pattern("a")
      p2 = unique_pattern("b")
      assert {:ok, r1} = RegexCache.get_or_compile(p1)
      assert {:ok, r2} = RegexCache.get_or_compile(p2)
      refute r1 == r2
    end

    test "compiled regex actually matches as expected" do
      pattern = unique_pattern("match") <> "\\d+"
      assert {:ok, regex} = RegexCache.get_or_compile(pattern)

      base = String.trim_trailing(pattern, "\\d+")

      assert Regex.match?(regex, "#{base}123")
      refute Regex.match?(regex, "nope")
    end

    test "caches many different patterns without losing recent hits" do
      # Exercise the handle_call path for cache hits + misses across many patterns.
      patterns = for i <- 1..50, do: "bulk_pattern_#{System.unique_integer()}_#{i}"

      for p <- patterns do
        assert {:ok, %Regex{}} = RegexCache.get_or_compile(p)
      end

      # All patterns should still be retrievable.
      for p <- patterns do
        assert {:ok, %Regex{}} = RegexCache.get_or_compile(p)
      end
    end

    test "exercises LRU eviction when cache grows past the max-size threshold" do
      # Generate enough patterns to push past @max_cache_size (1000).
      # Existing cache entries count too, so overshoot by ~150 to be safe.
      patterns = for i <- 1..1_200, do: "lru_pattern_#{System.unique_integer()}_#{i}"

      for p <- patterns do
        assert {:ok, %Regex{}} = RegexCache.get_or_compile(p)
      end

      # The first few patterns should likely be evicted, but still compile fine
      # on re-request (no caching contract violation).
      [first | _] = patterns
      assert {:ok, %Regex{}} = RegexCache.get_or_compile(first)
    end
  end
end
