defmodule AprsmeWeb.Api.V1.CallsignJSONTest do
  use ExUnit.Case, async: true

  alias Aprsme.Packet
  alias AprsmeWeb.Api.V1.CallsignJSON

  defp base_packet(attrs \\ %{}) do
    defaults = %{
      id: 1,
      base_callsign: "K5ABC",
      ssid: "9",
      sender: "K5ABC-9",
      destination: "APN382",
      path: "WIDE1-1",
      data_type: "position",
      data: %{"information_field" => "raw data"},
      raw_packet: "K5ABC-9>APN382,WIDE1-1:!3000.00N/09500.00W>test",
      received_at: ~U[2024-01-15 12:00:00Z],
      region: "US",
      has_position: true,
      lat: 30.5,
      lon: -95.0,
      course: 180,
      speed: 55.0,
      altitude: 100.0,
      symbol_code: ">",
      symbol_table_id: "/",
      comment: "test",
      timestamp: "t",
      aprs_messaging: false,
      temperature: nil,
      humidity: nil,
      wind_speed: nil,
      wind_direction: nil,
      wind_gust: nil,
      pressure: nil,
      rain_1h: nil,
      rain_24h: nil,
      rain_since_midnight: nil,
      device_identifier: nil,
      manufacturer: nil,
      equipment_type: nil,
      addressee: nil,
      message_text: nil,
      message_number: nil,
      inserted_at: ~N[2024-01-15 12:00:00],
      updated_at: ~N[2024-01-15 12:00:00]
    }

    struct(Packet, Map.merge(defaults, attrs))
  end

  describe "render show.json" do
    test "wraps the packet in a :data key" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.id == 1
    end

    test "includes callsign formatted with ssid" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.callsign == "K5ABC-9"
    end

    test "omits ssid from callsign when nil" do
      result = CallsignJSON.render("show.json", %{packet: base_packet(%{ssid: nil})})
      assert result.data.callsign == "K5ABC"
    end

    test "omits ssid '0' from callsign" do
      result = CallsignJSON.render("show.json", %{packet: base_packet(%{ssid: "0"})})
      assert result.data.callsign == "K5ABC"
    end

    test "extracts information_field from data map" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.information_field == "raw data"
    end

    test "handles nil data map without crashing" do
      result = CallsignJSON.render("show.json", %{packet: base_packet(%{data: nil})})
      assert result.data.information_field == nil
    end

    test "includes position with altitude/course/speed when present" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.position.latitude == 30.5
      assert result.data.position.longitude == -95.0
      assert result.data.position.altitude == 100.0
      assert result.data.position.course == 180
      assert result.data.position.speed == 55.0
    end

    test "omits altitude/course/speed from position when nil" do
      packet = base_packet(%{altitude: nil, course: nil, speed: nil})
      result = CallsignJSON.render("show.json", %{packet: packet})
      refute Map.has_key?(result.data.position, :altitude)
      refute Map.has_key?(result.data.position, :course)
      refute Map.has_key?(result.data.position, :speed)
    end

    test "returns nil position when has_position is false" do
      result = CallsignJSON.render("show.json", %{packet: base_packet(%{has_position: false})})
      assert result.data.position == nil
    end

    test "returns nil position when lat and lon are nil" do
      result = CallsignJSON.render("show.json", %{packet: base_packet(%{lat: nil, lon: nil})})
      assert result.data.position == nil
    end

    test "converts Decimal coordinates to floats" do
      packet = base_packet(%{lat: Decimal.new("30.5"), lon: Decimal.new("-95.0")})
      result = CallsignJSON.render("show.json", %{packet: packet})
      assert result.data.position.latitude == 30.5
      assert result.data.position.longitude == -95.0
    end

    test "returns nil symbol when both symbol fields missing" do
      packet = base_packet(%{symbol_code: nil, symbol_table_id: nil})
      result = CallsignJSON.render("show.json", %{packet: packet})
      assert result.data.symbol == nil
    end

    test "includes symbol when present" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.symbol == %{code: ">", table_id: "/"}
    end

    test "returns nil weather when all weather fields empty" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.weather == nil
    end

    test "includes only non-nil weather fields" do
      packet = base_packet(%{temperature: 72.0, humidity: 50.0})
      result = CallsignJSON.render("show.json", %{packet: packet})
      assert result.data.weather == %{temperature: 72.0, humidity: 50.0}
    end

    test "returns nil message when all message fields empty" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.message == nil
    end

    test "includes only non-nil message fields" do
      packet =
        base_packet(%{
          addressee: "BLN",
          message_text: "hello",
          message_number: "001"
        })

      result = CallsignJSON.render("show.json", %{packet: packet})
      assert result.data.message == %{addressee: "BLN", text: "hello", number: "001"}
    end

    test "excludes empty-string values via maybe_add" do
      packet = base_packet(%{comment: ""})
      result = CallsignJSON.render("show.json", %{packet: packet})
      # maybe_add drops empty strings, but comment is a top-level field added
      # unconditionally — so it's retained as-is at the top level.
      assert result.data.comment == ""
    end
  end

  describe "render index.json" do
    test "wraps a list of packets" do
      packets = [base_packet(), base_packet(%{id: 2, sender: "K5ABC-10"})]
      result = CallsignJSON.render("index.json", %{packets: packets})
      assert length(result.data) == 2
      assert Enum.map(result.data, & &1.id) == [1, 2]
    end

    test "handles an empty list" do
      assert CallsignJSON.render("index.json", %{packets: []}) == %{data: []}
    end
  end

  describe "render not_found.json" do
    test "returns nil data and a descriptive message" do
      result = CallsignJSON.render("not_found.json", %{callsign: "K5ABC"})
      assert result.data == nil
      assert result.message == "No packets found for callsign K5ABC"
    end
  end

  describe "equipment rendering" do
    test "returns nil equipment when no equipment data" do
      result = CallsignJSON.render("show.json", %{packet: base_packet()})
      assert result.data.equipment == nil
    end

    test "includes manufacturer/equipment_type when set (no device cache hit)" do
      packet =
        base_packet(%{
          manufacturer: "Yaesu",
          equipment_type: "FT1D",
          # "" avoids a DeviceCache.lookup_device/1 call (nil/"" short-circuits).
          device_identifier: ""
        })

      result = CallsignJSON.render("show.json", %{packet: packet})
      assert result.data.equipment.manufacturer == "Yaesu"
      assert result.data.equipment.equipment_type == "FT1D"
    end
  end
end
