defmodule Towerops.Workers.PollingOffsetTest do
  use ExUnit.Case, async: true

  alias Towerops.Workers.PollingOffset

  describe "calculate_offset/2" do
    test "returns value within interval bounds" do
      device_id = Ecto.UUID.generate()
      offset = PollingOffset.calculate_offset(device_id, 300)

      assert offset >= 0
      assert offset < 300
    end

    test "returns value within bounds for different intervals" do
      device_id = Ecto.UUID.generate()

      # Test various intervals
      for interval <- [60, 120, 300, 600, 3600] do
        offset = PollingOffset.calculate_offset(device_id, interval)
        assert offset >= 0, "offset should be >= 0 for interval #{interval}"
        assert offset < interval, "offset should be < #{interval} for interval #{interval}"
      end
    end

    test "is deterministic - same device_id always returns same offset" do
      device_id = Ecto.UUID.generate()

      offset1 = PollingOffset.calculate_offset(device_id, 300)
      offset2 = PollingOffset.calculate_offset(device_id, 300)
      offset3 = PollingOffset.calculate_offset(device_id, 300)

      assert offset1 == offset2
      assert offset2 == offset3
    end

    test "different devices get different offsets" do
      id1 = Ecto.UUID.generate()
      id2 = Ecto.UUID.generate()
      id3 = Ecto.UUID.generate()

      offset1 = PollingOffset.calculate_offset(id1, 300)
      offset2 = PollingOffset.calculate_offset(id2, 300)
      offset3 = PollingOffset.calculate_offset(id3, 300)

      # Very unlikely all three would be equal with good hash distribution
      # At least two should be different
      unique_offsets = Enum.uniq([offset1, offset2, offset3])
      assert length(unique_offsets) >= 2, "expected at least 2 different offsets from 3 devices"
    end

    test "offset changes with different intervals for same device" do
      device_id = Ecto.UUID.generate()

      offset_60 = PollingOffset.calculate_offset(device_id, 60)
      offset_300 = PollingOffset.calculate_offset(device_id, 300)
      offset_600 = PollingOffset.calculate_offset(device_id, 600)

      # Verify all within their respective bounds
      assert offset_60 < 60
      assert offset_300 < 300
      assert offset_600 < 600

      # They're likely different (not guaranteed, but very likely)
      # Just verify bounds are correct
    end

    test "handles minimum interval (1 second)" do
      device_id = Ecto.UUID.generate()
      offset = PollingOffset.calculate_offset(device_id, 1)

      assert offset == 0, "with interval of 1, offset must be 0"
    end

    test "distributes devices across interval buckets" do
      # Generate 100 device IDs
      device_ids = for _ <- 1..100, do: Ecto.UUID.generate()

      # Calculate offsets with 300 second interval
      offsets = Enum.map(device_ids, &PollingOffset.calculate_offset(&1, 300))

      # Divide 300 seconds into 10 buckets (0-29, 30-59, 60-89, etc.)
      buckets = Enum.group_by(offsets, &div(&1, 30))

      # With good distribution, each bucket should have roughly 10 devices (100/10)
      # Allow for variance - hash functions have natural clustering
      # Check that no single bucket has more than 30% of devices (pathological case)
      for {bucket_num, devices_in_bucket} <- buckets do
        count = length(devices_in_bucket)

        assert count <= 30,
               "bucket #{bucket_num} has #{count} devices (#{count}%), expected at most 30 (30%)"
      end

      # Verify we're actually using multiple buckets (good distribution)
      # With 100 devices and 10 buckets, expect at least 7 buckets to be used
      assert map_size(buckets) >= 7, "expected at least 7 of 10 buckets to be used, got #{map_size(buckets)}"
    end

    test "distributes devices across full interval range" do
      # Generate many device IDs
      device_ids = for _ <- 1..1000, do: Ecto.UUID.generate()

      # Calculate offsets
      offsets = Enum.map(device_ids, &PollingOffset.calculate_offset(&1, 300))

      # Verify we see offsets across the full range
      min_offset = Enum.min(offsets)
      max_offset = Enum.max(offsets)

      # With 1000 devices, we should see offsets near both ends
      assert min_offset < 30, "expected some offsets near 0, got min #{min_offset}"
      assert max_offset > 270, "expected some offsets near 299, got max #{max_offset}"
    end

    test "handles binary UUID format" do
      # Test with binary UUID (which is what Device.id actually is)
      binary_uuid = Ecto.UUID.dump!(Ecto.UUID.generate())
      offset = PollingOffset.calculate_offset(binary_uuid, 300)

      assert offset >= 0
      assert offset < 300
    end

    test "handles string UUID format" do
      # Test with string UUID
      string_uuid = Ecto.UUID.generate()
      offset = PollingOffset.calculate_offset(string_uuid, 300)

      assert offset >= 0
      assert offset < 300
    end
  end
end
