defmodule AprsmeWeb.InfoLive.ShowHelpersTest do
  use ExUnit.Case, async: true

  alias AprsmeWeb.InfoLive.Show

  describe "format_distance/2 with English locale (imperial)" do
    test "uses feet for distances under a mile" do
      # 0.5 km → ~0.31 miles → ~1641 ft
      result = Show.format_distance(0.5, "en")
      assert result =~ "ft"
      refute result =~ "mi"
    end

    test "uses miles for distances >= 1 mile" do
      # 10 km → ~6.21 miles
      result = Show.format_distance(10.0, "en")
      assert result =~ "mi"
      refute result =~ "ft"
    end

    test "default locale is English" do
      assert Show.format_distance(10.0) =~ "mi"
    end
  end

  describe "format_distance/2 with non-English locale (metric)" do
    test "uses meters for distances under 1 km" do
      result = Show.format_distance(0.5, "de")
      assert result =~ "m"
      refute result =~ "km"
    end

    test "uses km for longer distances" do
      result = Show.format_distance(5.0, "fr")
      assert result =~ "km"
    end

    test "handles multiple metric locales" do
      for loc <- ["es", "de", "fr", "ja"] do
        assert Show.format_distance(10.0, loc) =~ "km"
      end
    end
  end

  describe "calculate_course/4" do
    test "returns 0 (or 360) for due north" do
      # From (0,0) to (1,0) — moving north.
      bearing = Show.calculate_course(0.0, 0.0, 1.0, 0.0)
      # credo:disable-for-next-line Jump.CredoChecks.ConditionalAssertion
      assert bearing == 0.0 or bearing == 360.0
      # calculate_course for due north legitimately returns 0.0 or 360.0
    end

    test "returns ~90 for due east" do
      # From (0,0) to (0,1) — moving east.
      bearing = Show.calculate_course(0.0, 0.0, 0.0, 1.0)
      assert_in_delta bearing, 90.0, 0.5
    end

    test "returns ~180 for due south" do
      bearing = Show.calculate_course(1.0, 0.0, 0.0, 0.0)
      assert_in_delta bearing, 180.0, 0.5
    end

    test "returns ~270 for due west" do
      bearing = Show.calculate_course(0.0, 1.0, 0.0, 0.0)
      assert_in_delta bearing, 270.0, 0.5
    end

    test "always returns a value in 0..360" do
      for {lat1, lon1, lat2, lon2} <- [
            {0.0, 0.0, 45.0, 45.0},
            {10.0, 20.0, -10.0, -20.0},
            {0.0, 0.0, 0.0, -1.0}
          ] do
        b = Show.calculate_course(lat1, lon1, lat2, lon2)
        assert b >= 0
        assert b <= 360
      end
    end

    test "accepts Decimal inputs" do
      result = Show.calculate_course(Decimal.new("0.0"), Decimal.new("0.0"), Decimal.new("1.0"), Decimal.new("0.0"))
      assert is_float(result)
    end
  end

  describe "haversine/4" do
    test "returns 0 for identical points" do
      assert Show.haversine(40.0, -100.0, 40.0, -100.0) == 0.0
    end

    test "is symmetric" do
      a = Show.haversine(40.0, -100.0, 41.0, -99.0)
      b = Show.haversine(41.0, -99.0, 40.0, -100.0)
      assert_in_delta a, b, 0.001
    end

    test "returns positive distance for distinct points" do
      d = Show.haversine(40.0, -100.0, 41.0, -99.0)
      assert d > 0
    end
  end

  describe "parse_path_with_links/1" do
    test "returns empty list for non-binary input" do
      assert Show.parse_path_with_links(nil) == []
      assert Show.parse_path_with_links("") == []
      assert Show.parse_path_with_links(123) == []
    end

    test "splits on commas and links plain callsigns" do
      result = Show.parse_path_with_links("K5ABC-1,W1XYZ")
      assert [{:link, "K5ABC-1", "K5ABC-1"}, {:link, "W1XYZ", "W1XYZ"}] = result
    end

    test "preserves the heard-via asterisk in the display text" do
      assert [{:link, "K5ABC", "K5ABC*"}] = Show.parse_path_with_links("K5ABC*")
    end

    test "does not link WIDE/TRACE/RELAY/TCPIP path aliases" do
      assert [{:text, "WIDE1-1", "WIDE1-1"}] = Show.parse_path_with_links("WIDE1-1")
      assert [{:text, "TRACE3-3", "TRACE3-3"}] = Show.parse_path_with_links("TRACE3-3")
      assert [{:text, "RELAY", "RELAY"}] = Show.parse_path_with_links("RELAY")
      assert [{:text, "TCPIP*", "TCPIP*"}] = Show.parse_path_with_links("TCPIP*")
    end

    test "does not link q-constructs" do
      assert [{:text, "qAC", "qAC"}] = Show.parse_path_with_links("qAC")
      assert [{:text, "qAR", "qAR"}] = Show.parse_path_with_links("qAR")
    end

    test "trims whitespace before linking" do
      assert [{:link, "K5ABC", "K5ABC"}] = Show.parse_path_with_links(" K5ABC ")
    end

    test "mixes links and text in a single path" do
      result = Show.parse_path_with_links("K5ABC*,WIDE1-1,W1XYZ")

      assert [
               {:link, "K5ABC", "K5ABC*"},
               {:text, "WIDE1-1", "WIDE1-1"},
               {:link, "W1XYZ", "W1XYZ"}
             ] = result
    end
  end

  describe "render_symbol_html/2" do
    test "returns an empty raw for a nil packet" do
      result = Show.render_symbol_html(nil)
      # raw("") — a safe-iodata pair.
      assert {:safe, ""} = result
    end

    test "returns an overlay div for a packet with a uppercase-letter overlay table_id" do
      packet = %{symbol_table_id: "A", symbol_code: ">"}
      {:safe, html} = Show.render_symbol_html(packet, 24)
      assert html =~ "<div"
      assert html =~ "background-image"
    end

    test "returns a single-sprite div for a standard symbol table" do
      packet = %{symbol_table_id: "/", symbol_code: "-"}
      {:safe, html} = Show.render_symbol_html(packet)
      assert html =~ "<div"
    end
  end

  describe "haversine/4 with Decimal input" do
    test "handles Decimal-valued coordinates" do
      result =
        Show.haversine(
          Decimal.new("40.0"),
          Decimal.new("-100.0"),
          Decimal.new("41.0"),
          Decimal.new("-99.0")
        )

      assert is_float(result)
      assert result > 0
    end
  end

  describe "format_distance/2 rounding" do
    test "rounds to 2 decimal places in metric mode" do
      # 1.234567 km → "1.23 km"
      assert Show.format_distance(1.234567, "fr") == "1.23 km"
    end

    test "0 km in metric mode prints 0 m" do
      assert Show.format_distance(0.0, "de") == "0.0 m"
    end

    test "0 km in English mode prints 0 ft" do
      assert Show.format_distance(0.0, "en") == "0.0 ft"
    end
  end

  describe "position_changed_for_test/2" do
    test "returns true when current packet is nil" do
      assert Show.position_changed_for_test(nil, %{lat: 1.0, lon: 1.0})
    end

    test "returns false when new packet is nil" do
      refute Show.position_changed_for_test(%{lat: 1.0, lon: 1.0}, nil)
    end

    test "returns false when positions are identical" do
      current = %{lat: 33.0, lon: -96.0}
      new = %{lat: 33.0, lon: -96.0}
      refute Show.position_changed_for_test(current, new)
    end

    test "returns true when position changes by more than threshold" do
      current = %{lat: 33.0, lon: -96.0}
      new = %{lat: 33.01, lon: -96.0}
      assert Show.position_changed_for_test(current, new)
    end

    test "returns true when either coordinate is nil" do
      assert Show.position_changed_for_test(%{lat: nil, lon: -96.0}, %{lat: 33.0, lon: -96.0})
      assert Show.position_changed_for_test(%{lat: 33.0, lon: nil}, %{lat: 33.0, lon: -96.0})
      assert Show.position_changed_for_test(%{lat: 33.0, lon: -96.0}, %{lat: nil, lon: -96.0})
      assert Show.position_changed_for_test(%{lat: 33.0, lon: -96.0}, %{lat: 33.0, lon: nil})
    end
  end
end
