defmodule AprsmeWeb.MapLive.DataBuilderExtrasTest do
  @moduledoc """
  Coverage-targeted tests for `DataBuilder` private helpers reached only by
  edge-case input shapes.
  """
  use ExUnit.Case, async: true

  alias AprsmeWeb.MapLive.DataBuilder

  describe "build_simple_popup/2 with weather packets" do
    test "returns a binary HTML string for a weather packet" do
      weather_packet = %{
        "id" => "wxpkt-1",
        "sender" => "WX-1",
        "callsign" => "WX-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        "temperature" => 72.5,
        "humidity" => 50,
        "pressure" => 1013.2,
        "wind_speed" => 5.5,
        "wind_direction" => 180,
        "rain_1h" => 0.0
      }

      result = DataBuilder.build_simple_popup(weather_packet, true)
      assert byte_size(result) > 0
      assert String.length(result) > 0
    end

    test "returns a binary HTML string for a non-weather packet" do
      pos_packet = %{
        "id" => "pos-1",
        "sender" => "POS-1",
        "callsign" => "POS-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        "comment" => "Just a position"
      }

      result = DataBuilder.build_simple_popup(pos_packet, false)
      assert byte_size(result) > 0
    end
  end

  describe "select_best_packet_for_display/1 weather-only fallback" do
    test "falls back to the most recent weather packet when no position packets exist" do
      now = DateTime.utc_now()

      weather_only = [
        %{
          "id" => "wx1",
          "sender" => "WX-1",
          "callsign" => "WX-1",
          "received_at" => DateTime.add(now, -100, :second),
          "lat" => 33.0,
          "lon" => -96.0,
          "temperature" => 70.0,
          "humidity" => 50
        },
        %{
          "id" => "wx2",
          "sender" => "WX-1",
          "callsign" => "WX-1",
          "received_at" => now,
          "lat" => 33.0,
          "lon" => -96.0,
          "temperature" => 72.0,
          "humidity" => 51
        }
      ]

      result = DataBuilder.select_best_packet_for_display(weather_only)
      # The function should return a weather packet — either order is acceptable.
      assert %{"sender" => _} = result
    end
  end

  describe "build_packet_data with map-shaped data field" do
    test "builds without crashing when the packet's :data has tuple values" do
      # Tuple values exercise convert_tuples_to_strings 2/3-tuple branches.
      packet = %{
        "id" => "tup-1",
        "sender" => "T-1",
        "callsign" => "T-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        "data" => %{
          "two_tuple" => {1, 2},
          "three_tuple" => {1, 2, 3},
          "big_tuple" => {1, 2, 3, 4, 5}
        }
      }

      # Returns nil if the function rejects this shape (which is fine — the
      # convert_tuples_to_strings helpers run earlier as a side effect anyway).
      result = DataBuilder.build_packet_data(packet, true)

      if is_map(result) do
        assert %{"id" => _} = result
      else
        assert result == nil
      end
    end
  end

  describe "build_packet_data with invalid coordinates" do
    test "returns nil when lat is out of range" do
      packet = %{
        "id" => "bad-1",
        "sender" => "BAD-1",
        "callsign" => "BAD-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 99.9,
        "lon" => -96.0
      }

      assert is_nil(DataBuilder.build_packet_data(packet, true))
    end

    test "returns nil when lat/lon are non-numeric" do
      packet = %{
        "id" => "bad-2",
        "sender" => "BAD-2",
        "callsign" => "BAD-2",
        "received_at" => DateTime.utc_now(),
        "lat" => "not-a-number",
        "lon" => "also-bad"
      }

      assert is_nil(DataBuilder.build_packet_data(packet, true))
    end
  end

  describe "build_packet_data_list/1 with empty groups" do
    test "returns [] for an empty list" do
      assert DataBuilder.build_packet_data_list([]) == []
    end

    test "deduplicates packets with the same callsign_group" do
      now = DateTime.utc_now()

      packets =
        for i <- 1..3 do
          %{
            "id" => "dup-#{i}",
            "sender" => "DUP-1",
            "callsign" => "DUP-1",
            "callsign_group" => "DUP-1",
            "received_at" => DateTime.add(now, -i * 60, :second),
            "lat" => 33.0 + i * 0.001,
            "lon" => -96.0 + i * 0.001
          }
        end

      result = DataBuilder.build_packet_data_list(packets)
      assert is_list(result) and result != []
    end
  end

  describe "build_packet_result without :id falls back to callsign-based ID" do
    test "live packet without :id uses callsign in the generated ID" do
      # When packet has no :id key, the build_packet_result helper uses the
      # callsign to generate the marker ID (line 473 in data_builder.ex).
      packet = %{
        "sender" => "NOID-1",
        "callsign" => "NOID-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        "comment" => "no id"
      }

      result = DataBuilder.build_packet_data(packet, true)

      if is_map(result) do
        # Build should succeed with a generated id beginning with "live_".
        assert byte_size(result["id"]) > 0
        assert String.starts_with?(result["id"], "live_")
      else
        assert result == nil
      end
    end
  end

  describe "convert_tuples_to_strings via popup data with deeply nested tuples" do
    test "tuples of size > 3 fall through to inspect/1" do
      packet = %{
        "id" => "big-tup",
        "sender" => "BIG-1",
        "callsign" => "BIG-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        # Tuples of size 4 and 5 trigger the inspect/1 fallback in
        # convert_tuples_to_strings/1 (line 587).
        "data" => %{
          "quad" => {1, 2, 3, 4},
          "five" => {1, 2, 3, 4, 5}
        }
      }

      result = DataBuilder.build_simple_popup(packet, false)
      assert byte_size(result) > 0
    end

    test "list of tuples is processed via the list head" do
      packet = %{
        "id" => "list-tup",
        "sender" => "LST-1",
        "callsign" => "LST-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        "data" => [
          {1, 2},
          {3, 4, 5}
        ]
      }

      result = DataBuilder.build_simple_popup(packet, false)
      assert byte_size(result) > 0
    end
  end

  describe "build_packet_data with string-typed weather values exercises convert_unit" do
    test "string numeric weather values parse via Float.parse branch" do
      packet = %{
        "id" => "str-wx",
        "sender" => "STR-1",
        "callsign" => "STR-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        "temperature" => "72.5",
        "humidity" => "50",
        "pressure" => "1013.2",
        "wind_speed" => "10.0",
        "rain_1h" => "0.05"
      }

      result = DataBuilder.build_simple_popup(packet, true)
      assert byte_size(result) > 0
    end

    test "non-parseable string weather values fall through to {value, default_unit}" do
      packet = %{
        "id" => "junk-wx",
        "sender" => "JUNK-1",
        "callsign" => "JUNK-1",
        "received_at" => DateTime.utc_now(),
        "lat" => 33.0,
        "lon" => -96.0,
        "temperature" => "abc",
        "humidity" => "?",
        "pressure" => "??",
        "wind_speed" => "n/a",
        "rain_1h" => "trace"
      }

      result = DataBuilder.build_simple_popup(packet, true)
      assert byte_size(result) > 0
    end
  end
end
