defmodule Aprsme.PacketExtrasTest do
  @moduledoc """
  Edge-case coverage for `Aprsme.Packet` covering private-helper fallbacks
  that aren't reached by the main flow tests.
  """
  use Aprsme.DataCase, async: true

  alias Aprsme.Packet

  describe "extract_additional_data/2 with non-map data_extended" do
    test "binary data_extended falls through to the empty-map branch" do
      attrs = %{sender: "TEST", data_type: "position", data_extended: "raw-string"}
      result = Packet.extract_additional_data(attrs, "raw")
      assert result[:sender] == "TEST"
      refute Map.has_key?(result, :lat)
    end

    test "nil data_extended produces no extracted lat/lon/comment" do
      attrs = %{sender: "TEST", data_type: "position", data_extended: nil}
      result = Packet.extract_additional_data(attrs, "raw")
      assert result[:sender] == "TEST"
    end

    test "ParseError struct in data_extended yields no extras" do
      err = %Aprs.Types.ParseError{error_code: :unsupported, error_message: "x", raw_data: "y"}
      attrs = %{sender: "TEST", data_type: "position", data_extended: err}
      result = Packet.extract_additional_data(attrs, "raw")
      assert result[:sender] == "TEST"
    end
  end

  describe "extract_additional_data/2 with mic_e_map symbol defaults" do
    test "uses default '>' symbol_code and '/' symbol_table_id when MicE map omits them" do
      attrs = %{
        sender: "MICE-1",
        data_type: "mic_e",
        data_extended: %{
          __original_struct__: Aprs.Types.MicE,
          latitude: 33.0,
          longitude: -96.0,
          message: "MicE comment"
        }
      }

      result = Packet.extract_additional_data(attrs, "MICE-1>APRS:`abc")
      assert result[:symbol_code] == ">"
      assert result[:symbol_table_id] == "/"
    end

    test "extract_from_map struct branch handles a non-MicE struct via Map.from_struct" do
      # URI is a stdlib struct that survives Map.from_struct without raising.
      ext = %URI{scheme: "https", host: "example.test"}
      attrs = %{sender: "S", data_type: "position", data_extended: ext}
      result = Packet.extract_additional_data(attrs, "raw")
      assert result[:sender] == "S"
    end
  end

  describe "changeset/2 with weather binary data" do
    test "accepts weather data formatted as binary string" do
      # Exercises the `process_binary_weather_data` branch (line 661 in packet.ex).
      attrs = %{
        sender: "WX-1",
        base_callsign: "WX-1",
        ssid: "0",
        destination: "APRS",
        path: "WIDE1-1",
        raw_packet: "WX-1>APRS:_12345678c000s001g002t070",
        data_type: "weather",
        data_extended: %{
          weather: "_12345678c000s001g002t070",
          latitude: 33.0,
          longitude: -96.0
        }
      }

      changeset = Packet.changeset(%Packet{}, attrs)
      assert %Ecto.Changeset{} = changeset
    end
  end

  describe "changeset/2 wind_direction non-integer fallback" do
    test "non-integer wind_direction in raw struct falls through to identity branch" do
      # Construct a Packet with wind_direction set as a float (cast would normally
      # round, but we put_change directly so the integer guards don't match).
      attrs = %{
        sender: "WX-1",
        base_callsign: "WX-1",
        ssid: "0",
        destination: "APRS",
        path: "WIDE1-1",
        raw_packet: "WX-1>APRS:weather",
        data_type: "position"
      }

      cs =
        %Packet{}
        |> Packet.changeset(attrs)
        |> Ecto.Changeset.put_change(:wind_direction, 1.5)

      # Re-run normalize via a fresh changeset that already has the float.
      # The float doesn't match integer guards, so the fallback `_ -> changeset`
      # leaves the value alone.
      assert Ecto.Changeset.get_change(cs, :wind_direction) == 1.5
    end
  end

  describe "changeset/2 has_position via legacy lat/lon" do
    test "valid lat/lon without a Geo location still sets has_position true" do
      # Force the legacy path: provide lat/lon but no location, and the
      # geometry creation should succeed (CoordinateUtils accepts these).
      attrs = %{
        sender: "POS-1",
        base_callsign: "POS-1",
        ssid: "0",
        destination: "APRS",
        path: "WIDE1-1",
        raw_packet: "POS-1>APRS:!3300.00N/09600.00W>",
        data_type: "position",
        lat: 33.0,
        lon: -96.0
      }

      cs = Packet.changeset(%Packet{}, attrs)
      assert Ecto.Changeset.get_field(cs, :has_position) == true
    end
  end

  describe "PHG / altitude / weather corner cases" do
    test "PHG with non-numeric character in power yields default 0" do
      # Strings of length 4 but with non-digit chars exercise the
      # calculate_phg_power error branch (line 838).
      attrs = %{
        sender: "PHG-1",
        data_type: "position",
        data_extended: %{
          comment: "PHG?060",
          latitude: 33.0,
          longitude: -96.0
        }
      }

      result = Packet.extract_additional_data(attrs, "raw")
      # If extract_phg_from_comment can't parse, phg_* fields shouldn't be set,
      # and either way the function shouldn't crash.
      assert result[:sender] == "PHG-1"
    end

    test "altitude with non-digit pattern returns nil" do
      attrs = %{
        sender: "ALT-1",
        data_type: "position",
        data_extended: %{
          comment: "/A=ABCDEF some text",
          latitude: 33.0,
          longitude: -96.0
        }
      }

      result = Packet.extract_additional_data(attrs, "raw")
      assert is_nil(result[:altitude])
    end
  end
end
