defmodule Parser.PositionTest do
  use ExUnit.Case, async: true
  use ExUnitProperties

  alias Parser.Position
  alias Parser.Types.Position, as: PositionStruct

  describe "parse/1" do
    test "returns a Position struct for valid input" do
      result = Position.parse("4903.50N/07201.75W>Test position")
      assert %PositionStruct{} = result
      assert result.symbol_table_id == "/"
      assert result.symbol_code == ">"
      assert result.comment == "Test position"
    end

    test "returns nil or struct with nil lat/lon for invalid input" do
      for input <- ["", "invalidstring", "12345678N/123456789W"] do
        result = Position.parse(input)
        assert result == nil or match?(%PositionStruct{latitude: nil, longitude: nil}, result)
      end
    end
  end

  property "returns nil or struct with nil lat/lon for random invalid strings" do
    check all s <- StreamData.string(:ascii, min_length: 1, max_length: 30),
              not String.match?(s, ~r/^\d{4}\.\d{2}[NS][\/\\]\d{5}\.\d{2}[EW].+/) do
      result = Position.parse(s)
      assert result == nil or match?(%PositionStruct{latitude: nil, longitude: nil}, result)
    end
  end

  describe "parse_aprs_position/2" do
    test "parses valid APRS lat/lon strings" do
      result = Position.parse_aprs_position("4903.50N", "07201.75W")

      if result.latitude == nil or result.longitude == nil do
        flunk("parse_aprs_position/2 returned nil for latitude or longitude")
      end

      assert Decimal.equal?(Decimal.round(result.latitude, 6), Decimal.new("49.058333"))
      assert Decimal.equal?(Decimal.round(result.longitude, 6), Decimal.new("-72.029167"))
    end

    test "returns nils for invalid strings" do
      assert %{latitude: nil, longitude: nil} = Position.parse_aprs_position("bad", "data")
    end
  end

  describe "calculate_position_ambiguity/2" do
    test "returns correct ambiguity for no spaces" do
      assert Position.calculate_position_ambiguity("4903.50N", "07201.75W") == 0
    end

    test "returns correct ambiguity for one space in each string" do
      assert Position.calculate_position_ambiguity("49 3.50N", "07201.7 W") == 1
    end

    test "returns correct ambiguity for two spaces in each string" do
      assert Position.calculate_position_ambiguity("4  3.50N", "0720  .7W") == 2
    end
  end

  describe "count_spaces/1" do
    property "counts spaces correctly" do
      check all s <- StreamData.string(:ascii, min_length: 0, max_length: 20) do
        assert Position.count_spaces(s) == s |> String.graphemes() |> Enum.count(&(&1 == " "))
      end
    end
  end

  describe "parse_dao_extension/1" do
    test "parses valid DAO extension" do
      assert %{lat_dao: "A", lon_dao: "B", datum: "WGS84"} = Position.parse_dao_extension("!ABZ!")
    end

    test "returns nil for no DAO extension" do
      assert Position.parse_dao_extension("no dao here") == nil
    end
  end
end
