defmodule Ecto.Integration.VersionstampTest do
  use Ecto.Integration.Case, async: true

  alias Ecto.Adapters.FoundationDB

  alias EctoFoundationDB.Exception.Unsupported
  alias EctoFoundationDB.Future
  alias EctoFoundationDB.Schemas.QueueItem
  alias EctoFoundationDB.Versionstamp

  alias Ecto.Integration.TestRepo

  describe "non-autogenerated primary keys" do
    test "nil pk raises", context do
      tenant = context[:tenant]

      assert_raise Unsupported,
                   ~r/does not support inserting records with nil primary keys/,
                   fn ->
                     %QueueItem{data: "foo"}
                     |> FoundationDB.usetenant(tenant)
                     |> TestRepo.insert()
                   end
    end

    test "internal transaction versionstamp", context do
      tenant = context[:tenant]

      {:ok, event} =
        %QueueItem{
          id: Versionstamp.next(),
          data: "foo"
        }
        |> FoundationDB.usetenant(tenant)
        |> TestRepo.insert()

      assert_raise Unsupported,
                   ~r/we disallow converting an incomplete versionstamp to an integer/,
                   fn ->
                     Versionstamp.to_integer(event.id)
                   end
    end

    test "explicit transaction versionstamp", context do
      tenant = context[:tenant]

      {event, vs_future} =
        TestRepo.transactional(
          tenant,
          fn tx ->
            assert_raise Unsupported,
                         ~r/you must use `EctoFoundationDB.Versionstamp.next\/1`/,
                         fn ->
                           Versionstamp.next()
                         end

            {:ok, event} =
              %QueueItem{
                id: Versionstamp.next(tx),
                data: "foo"
              }
              |> TestRepo.insert()

            vs_future = Versionstamp.get(tx)
            {event, vs_future}
          end
        )

      vs = Future.result(vs_future)
      event = %{event | id: Versionstamp.resolve(event.id, vs)}

      assert is_integer(Versionstamp.to_integer(event.id))
    end

    test "Repo.async_insert_all!", context do
      tenant = context[:tenant]

      future =
        TestRepo.transactional(
          tenant,
          fn tx ->
            TestRepo.async_insert_all!(QueueItem, [
              %QueueItem{
                id: Versionstamp.next(tx),
                data: "bar"
              },
              %QueueItem{
                id: Versionstamp.next(tx),
                data: "foo"
              }
            ])
          end
        )

      [event1, event2] = TestRepo.await(future)

      refute is_nil(event1.__meta__.prefix)
      refute is_nil(event2.__meta__.prefix)
      assert is_integer(Versionstamp.to_integer(event1.id))
      assert is_integer(Versionstamp.to_integer(event2.id))
    end
  end

  test "basic queue operations", context do
    tenant = context[:tenant]

    # push
    future =
      TestRepo.transactional(tenant, fn ->
        TestRepo.async_insert_all!(QueueItem, [
          %QueueItem{data: "test_a"},
          %QueueItem{data: "test_b"}
        ])
      end)

    [%QueueItem{id: id_a}, %QueueItem{id: id_b}] = TestRepo.await(future)

    # top
    assert [%QueueItem{id: ^id_a}] = TestRepo.all(QueueItem, limit: 1, prefix: tenant)

    assert %QueueItem{} = TestRepo.get!(QueueItem, id_a, prefix: tenant)

    # pop (and top comes for free)
    top =
      TestRepo.transactional(tenant, fn ->
        [top] = TestRepo.all(QueueItem, limit: 1)
        TestRepo.delete!(%QueueItem{id: top.id})
      end)

    assert %QueueItem{id: ^id_a} = top

    # next top
    assert [%QueueItem{id: ^id_b}] = TestRepo.all(QueueItem, limit: 1, prefix: tenant)
  end

  test "versionstamp schema with index", context do
    tenant = context[:tenant]

    future =
      TestRepo.transactional(tenant, fn ->
        TestRepo.async_insert_all!(QueueItem, [
          %QueueItem{author: "Alice", data: "test_a"},
          %QueueItem{author: "Bob", data: "test_b"}
        ])
      end)

    [%QueueItem{id: id_a}, %QueueItem{id: id_b}] = TestRepo.await(future)

    assert %QueueItem{id: ^id_a} = TestRepo.get_by!(QueueItem, [author: "Alice"], prefix: tenant)
    assert %QueueItem{id: ^id_b} = TestRepo.get_by!(QueueItem, [author: "Bob"], prefix: tenant)
  end
end
