defmodule Microwaveprop.RoverLocationsTest do
  use Microwaveprop.DataCase, async: true

  alias Microwaveprop.Rover
  alias Microwaveprop.Rover.Location

  defp user_fixture do
    Microwaveprop.AccountsFixtures.user_fixture()
  end

  describe "list_locations/0" do
    test "returns all locations across all users, newest first" do
      a = user_fixture()
      b = user_fixture()

      {:ok, _l1} = Rover.create_location(a, %{lat: 32.5, lon: -97.5, status: :good})
      {:ok, _l2} = Rover.create_location(b, %{lat: 33.0, lon: -96.5, status: :bad})

      result = Rover.list_locations()
      assert length(result) == 2
      assert Enum.all?(result, &match?(%Location{}, &1))
    end
  end

  describe "create_location/2" do
    test "stores the creator's user_id" do
      user = user_fixture()
      {:ok, loc} = Rover.create_location(user, %{lat: 32.5, lon: -97.5, status: :good})
      assert loc.user_id == user.id
    end

    test "supports notes and the bad status" do
      user = user_fixture()

      {:ok, loc} =
        Rover.create_location(user, %{
          lat: 32.0,
          lon: -97.0,
          status: :bad,
          notes: "private property"
        })

      assert loc.status == :bad
      assert loc.notes == "private property"
    end

    test "rejects out-of-range coordinates" do
      user = user_fixture()
      assert {:error, %Ecto.Changeset{}} = Rover.create_location(user, %{lat: 100.0, lon: 0.0, status: :good})
    end

    test "requires lat and lon" do
      user = user_fixture()
      assert {:error, cs} = Rover.create_location(user, %{status: :good})
      assert "can't be blank" in errors_on(cs).lat
    end
  end

  describe "Location.changeset/2" do
    test "rejects lon out of range" do
      changeset = Location.changeset(%Location{}, %{lat: 0.0, lon: 200.0, status: :good})
      refute changeset.valid?
      assert "must be less than or equal to 180.0" in errors_on(changeset).lon
    end

    test "rejects lat out of range" do
      changeset = Location.changeset(%Location{}, %{lat: -91.0, lon: 0.0, status: :good})
      refute changeset.valid?
      assert "must be greater than or equal to -90.0" in errors_on(changeset).lat
    end

    test "rejects invalid status" do
      changeset = Location.changeset(%Location{}, %{lat: 0.0, lon: 0.0, status: :unknown})
      refute changeset.valid?
      assert "is invalid" in errors_on(changeset).status
    end

    test "accepts valid lat/lon at the extreme boundaries" do
      changeset =
        Location.changeset(%Location{}, %{lat: 90.0, lon: 180.0, status: :good})

      assert changeset.valid?
    end

    test "notes exceeding max length are rejected" do
      changeset =
        Location.changeset(%Location{}, %{lat: 0.0, lon: 0.0, status: :good, notes: String.duplicate("x", 4001)})

      refute changeset.valid?
      assert "should be at most 4000 character(s)" in errors_on(changeset).notes
    end

    test "statuses/0 returns available status atoms" do
      assert Location.statuses() == [:good, :bad]
    end
  end

  describe "update_location/3 and delete_location/2" do
    test "creator can update their location" do
      user = user_fixture()
      {:ok, loc} = Rover.create_location(user, %{lat: 32.0, lon: -97.0, status: :good})

      assert {:ok, updated} = Rover.update_location(user, loc.id, %{notes: "updated"})
      assert updated.notes == "updated"
    end

    test "non-creator cannot update or delete" do
      user = user_fixture()
      other = user_fixture()
      {:ok, loc} = Rover.create_location(user, %{lat: 32.0, lon: -97.0, status: :good})

      assert {:error, :not_found} = Rover.update_location(other, loc.id, %{notes: "x"})
      assert {:error, :not_found} = Rover.delete_location(other, loc.id)
    end

    test "creator can delete their location" do
      user = user_fixture()
      {:ok, loc} = Rover.create_location(user, %{lat: 32.0, lon: -97.0, status: :good})

      assert {:ok, _} = Rover.delete_location(user, loc.id)
      refute Enum.any?(Rover.list_locations(), &(&1.id == loc.id))
    end
  end
end
