defmodule WhoNeedHelp.Tracking.Position do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  schema "tracking_positions" do
    field :position, Geo.PostGIS.Geometry
    field :accuracy_meters, :float
    field :captured_at, :utc_datetime
    belongs_to :tracking_session, WhoNeedHelp.Tracking.TrackingSession
    timestamps(type: :utc_datetime)
  end

  def changeset(position, attrs) do
    position
    |> cast(attrs, [:accuracy_meters, :captured_at])
    |> put_position(attrs)
    |> validate_required([:tracking_session_id, :position, :captured_at])
    |> validate_number(:accuracy_meters, greater_than_or_equal_to: 0)
    |> unique_constraint(:tracking_session_id)
  end

  defp put_position(changeset, attrs) do
    lat = attrs["latitude"] || attrs[:latitude]
    lng = attrs["longitude"] || attrs[:longitude]

    with {:ok, lat} <- parse_coordinate(lat),
         {:ok, lng} <- parse_coordinate(lng),
         true <- lat >= -90 and lat <= 90 and lng >= -180 and lng <= 180 do
      put_change(changeset, :position, %Geo.Point{coordinates: {lng, lat}, srid: 4326})
    else
      _ -> add_error(changeset, :position, "is invalid")
    end
  end

  defp parse_coordinate(value) when is_float(value), do: {:ok, value}
  defp parse_coordinate(value) when is_integer(value), do: {:ok, value * 1.0}

  defp parse_coordinate(value) when is_binary(value) do
    case Float.parse(value) do
      {coordinate, ""} -> {:ok, coordinate}
      _ -> :error
    end
  end

  defp parse_coordinate(_value), do: :error
end
