defmodule Commanded.EventStore.SubscriptionTestCase do
  import Commanded.SharedTestCase

  define_tests do
    alias Commanded.EventStore.{EventData, RecordedEvent, Subscriber}
    alias Commanded.Helpers.ProcessHelper
    alias Commanded.UUID

    defmodule BankAccountOpened do
      @derive Jason.Encoder
      defstruct [:account_number, :initial_balance]
    end

    describe "transient subscription to single stream" do
      test "should receive events appended to the stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        stream_uuid = UUID.uuid4()

        assert :ok = event_store.subscribe(event_store_meta, stream_uuid)

        :ok = event_store.append_to_stream(event_store_meta, stream_uuid, 0, build_events(1))

        received_events = assert_receive_events(event_store, event_store_meta, count: 1, from: 1)

        for %RecordedEvent{} = event <- received_events do
          assert event.stream_id == stream_uuid
          assert %DateTime{} = event.created_at
        end

        assert Enum.map(received_events, & &1.stream_version) == [1]

        :ok = event_store.append_to_stream(event_store_meta, stream_uuid, 1, build_events(2))

        received_events = assert_receive_events(event_store, event_store_meta, count: 2, from: 2)

        for %RecordedEvent{} = event <- received_events do
          assert event.stream_id == stream_uuid
          assert %DateTime{} = event.created_at
        end

        assert Enum.map(received_events, & &1.stream_version) == [2, 3]

        :ok = event_store.append_to_stream(event_store_meta, stream_uuid, 3, build_events(3))

        received_events = assert_receive_events(event_store, event_store_meta, count: 3, from: 4)

        for %RecordedEvent{} = event <- received_events do
          assert event.stream_id == stream_uuid
          assert %DateTime{} = event.created_at
        end

        assert Enum.map(received_events, & &1.stream_version) == [4, 5, 6]

        refute_receive {:events, _received_events}
      end

      test "should not receive events appended to another stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        stream_uuid = UUID.uuid4()
        another_stream_uuid = UUID.uuid4()

        assert :ok = event_store.subscribe(event_store_meta, stream_uuid)

        :ok =
          event_store.append_to_stream(event_store_meta, another_stream_uuid, 0, build_events(1))

        :ok =
          event_store.append_to_stream(event_store_meta, another_stream_uuid, 1, build_events(2))

        refute_receive {:events, _received_events}
      end
    end

    describe "transient subscription to all streams" do
      test "should receive events appended to any stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        assert :ok = event_store.subscribe(event_store_meta, :all)

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))

        received_events = assert_receive_events(event_store, event_store_meta, count: 1, from: 1)
        assert Enum.map(received_events, & &1.stream_id) == ["stream1"]
        assert Enum.map(received_events, & &1.stream_version) == [1]

        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))

        received_events = assert_receive_events(event_store, event_store_meta, count: 2, from: 2)
        assert Enum.map(received_events, & &1.stream_id) == ["stream2", "stream2"]
        assert Enum.map(received_events, & &1.stream_version) == [1, 2]

        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        received_events = assert_receive_events(event_store, event_store_meta, count: 3, from: 4)
        assert Enum.map(received_events, & &1.stream_id) == ["stream3", "stream3", "stream3"]
        assert Enum.map(received_events, & &1.stream_version) == [1, 2, 3]

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 1, build_events(2))

        received_events = assert_receive_events(event_store, event_store_meta, count: 2, from: 7)
        assert Enum.map(received_events, & &1.stream_id) == ["stream1", "stream1"]
        assert Enum.map(received_events, & &1.stream_version) == [2, 3]

        refute_receive {:events, _received_events}
      end
    end

    describe "persistent subscription to a single stream" do
      test "should receive `:subscribed` message once subscribed", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, "stream1", "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription}
      end

      test "should receive events appended to stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, "stream1", "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 1, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 3, build_events(3))

        assert_receive_events(event_store, event_store_meta, subscription, count: 1, from: 1)
        assert_receive_events(event_store, event_store_meta, subscription, count: 2, from: 2)
        assert_receive_events(event_store, event_store_meta, subscription, count: 3, from: 4)

        refute_receive {:events, _received_events}
      end

      test "should not receive events appended to another stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, "stream1", "subscriber", self(), :origin, [])

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        assert_receive_events(event_store, event_store_meta, subscription, count: 1, from: 1)
        refute_receive {:events, _received_events}
      end

      test "should skip existing events when subscribing from current position", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 1, build_events(2))

        wait_for_event_store()

        {:ok, subscription} =
          event_store.subscribe_to(
            event_store_meta,
            "stream1",
            "subscriber",
            self(),
            :current,
            []
          )

        assert_receive {:subscribed, ^subscription}
        refute_receive {:events, _events}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 3, build_events(3))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(3))
        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        assert_receive_events(event_store, event_store_meta, subscription, count: 3, from: 4)
        refute_receive {:events, _events}
      end

      test "should receive events already appended to stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, "stream3", "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription}

        assert_receive_events(event_store, event_store_meta, subscription, count: 3, from: 1)

        :ok = event_store.append_to_stream(event_store_meta, "stream3", 3, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream3", 4, build_events(1))

        assert_receive_events(event_store, event_store_meta, subscription, count: 2, from: 4)
        refute_receive {:events, _received_events}
      end

      test "should prevent duplicate subscriptions", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, _subscription} =
          event_store.subscribe_to(event_store_meta, "stream1", "subscriber", self(), :origin, [])

        assert {:error, :subscription_already_exists} ==
                 event_store.subscribe_to(
                   event_store_meta,
                   "stream1",
                   "subscriber",
                   self(),
                   :origin,
                   []
                 )
      end
    end

    describe "persistent subscription to all streams" do
      test "should receive `:subscribed` message once subscribed", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription}
      end

      test "should receive events appended to any stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        assert_receive_events(event_store, event_store_meta, subscription, count: 1, from: 1)
        assert_receive_events(event_store, event_store_meta, subscription, count: 2, from: 2)
        assert_receive_events(event_store, event_store_meta, subscription, count: 3, from: 4)

        refute_receive {:events, _received_events}
      end

      test "should receive events already appended to any stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))

        wait_for_event_store()

        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription}

        assert_receive_events(event_store, event_store_meta, subscription, count: 1, from: 1)
        assert_receive_events(event_store, event_store_meta, subscription, count: 2, from: 2)

        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        assert_receive_events(event_store, event_store_meta, subscription, count: 3, from: 4)
        refute_receive {:events, _received_events}
      end

      test "should skip existing events when subscribing from current position", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))

        wait_for_event_store()

        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :current, [])

        assert_receive {:subscribed, ^subscription}
        refute_receive {:events, _received_events}

        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        assert_receive_events(event_store, event_store_meta, subscription, count: 3, from: 4)

        refute_receive {:events, _received_events}
      end

      test "should prevent duplicate subscriptions", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, _subscription} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert {:error, :subscription_already_exists} ==
                 event_store.subscribe_to(
                   event_store_meta,
                   :all,
                   "subscriber",
                   self(),
                   :origin,
                   []
                 )
      end
    end

    describe "persistent subscription concurrency" do
      test "should allow multiple subscribers to single subscription", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscriber1} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 2)

        {:ok, subscriber2} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 2)

        assert_receive {:subscribed, ^subscriber1, _subscription1}
        assert_receive {:subscribed, ^subscriber2, _subscription2}
        refute_receive {:subscribed, _subscriber, _subscription}
      end

      test "should prevent too many subscribers to single subscription", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscriber1} = Subscriber.start_link(event_store, event_store_meta, self())

        {:ok, subscriber2} = Subscriber.start_link(event_store, event_store_meta, self())

        assert_receive {:subscribed, ^subscriber1, _subscription1}
        assert_receive {:subscribe_error, :subscription_already_exists, ^subscriber2}
        refute_receive {:subscribed, _subscriber, _subscription}
      end

      test "should prevent too many subscribers to subscription with concurrency limit", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscriber1} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 3)

        {:ok, subscriber2} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 3)

        {:ok, subscriber3} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 3)

        {:ok, subscriber4} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 3)

        assert_receive {:subscribed, ^subscriber1, _subscription1}
        assert_receive {:subscribed, ^subscriber2, _subscription1}
        assert_receive {:subscribed, ^subscriber3, _subscription1}
        assert_receive {:subscribe_error, :too_many_subscribers, ^subscriber4}
        refute_receive {:subscribed, _subscriber, _subscription}
      end

      test "should distribute events amongst subscribers", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscriber1} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 3)

        {:ok, subscriber2} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 3)

        {:ok, subscriber3} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 3)

        assert_receive {:subscribed, ^subscriber1, _subscription1}
        assert_receive {:subscribed, ^subscriber2, _subscription2}
        assert_receive {:subscribed, ^subscriber3, _subscription3}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(6))

        subscribers =
          for n <- 1..6 do
            assert_receive {:events, subscriber, [%RecordedEvent{event_number: ^n}] = events}

            :ok = Subscriber.ack(subscriber, events)

            subscriber
          end
          |> Enum.uniq()

        refute_receive {:events, _subscriber, _received_events}

        assert length(subscribers) == 3
      end

      test "should distribute events to subscribers using optional partition by function", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        opts = [
          concurrency_limit: 3,
          partition_by: fn %RecordedEvent{stream_id: stream_id} -> stream_id end
        ]

        {:ok, subscriber1} = Subscriber.start_link(event_store, event_store_meta, self(), opts)
        {:ok, subscriber2} = Subscriber.start_link(event_store, event_store_meta, self(), opts)
        {:ok, subscriber3} = Subscriber.start_link(event_store, event_store_meta, self(), opts)

        assert_receive {:subscribed, ^subscriber1, _subscription1}
        assert_receive {:subscribed, ^subscriber2, _subscription2}
        assert_receive {:subscribed, ^subscriber3, _subscription3}

        :ok = event_store.append_to_stream(event_store_meta, "stream0", 0, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))

        :ok = event_store.append_to_stream(event_store_meta, "stream0", 2, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 2, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 2, build_events(2))

        :ok = event_store.append_to_stream(event_store_meta, "stream0", 4, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 4, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 4, build_events(1))

        assert_receive {:events, subscriber1,
                        [%RecordedEvent{event_number: 1, stream_id: "stream0"}] = events1}

        assert_receive {:events, subscriber2,
                        [%RecordedEvent{event_number: 3, stream_id: "stream1"}] = events2}

        assert_receive {:events, subscriber3,
                        [%RecordedEvent{event_number: 5, stream_id: "stream2"}] = events3}

        refute_receive {:events, _subscriber, _received_events}

        :ok = Subscriber.ack(subscriber1, events1)
        :ok = Subscriber.ack(subscriber2, events2)
        :ok = Subscriber.ack(subscriber3, events3)

        assert_receive {:events, ^subscriber1,
                        [%RecordedEvent{event_number: 2, stream_id: "stream0"}] = events4}

        assert_receive {:events, ^subscriber2,
                        [%RecordedEvent{event_number: 4, stream_id: "stream1"}] = events5}

        assert_receive {:events, ^subscriber3,
                        [%RecordedEvent{event_number: 6, stream_id: "stream2"}] = events6}

        refute_receive {:events, _subscriber, _received_events}

        :ok = Subscriber.ack(subscriber1, events4)
        :ok = Subscriber.ack(subscriber2, events5)
        :ok = Subscriber.ack(subscriber3, events6)

        assert_receive {:events, ^subscriber1,
                        [%RecordedEvent{event_number: 7, stream_id: "stream0"}] = events7}

        assert_receive {:events, ^subscriber2,
                        [%RecordedEvent{event_number: 9, stream_id: "stream1"}] = events8}

        assert_receive {:events, ^subscriber3,
                        [%RecordedEvent{event_number: 11, stream_id: "stream2"}] = events9}

        refute_receive {:events, _subscriber, _received_events}

        :ok = Subscriber.ack(subscriber1, events7)
        :ok = Subscriber.ack(subscriber2, events8)
        :ok = Subscriber.ack(subscriber3, events9)

        assert_receive {:events, ^subscriber1,
                        [%RecordedEvent{event_number: 8, stream_id: "stream0"}] = events10}

        assert_receive {:events, ^subscriber2,
                        [%RecordedEvent{event_number: 10, stream_id: "stream1"}] = events11}

        assert_receive {:events, ^subscriber3,
                        [%RecordedEvent{event_number: 12, stream_id: "stream2"}] = events12}

        refute_receive {:events, _subscriber, _received_events}

        :ok = Subscriber.ack(subscriber1, events10)
        :ok = Subscriber.ack(subscriber2, events11)
        :ok = Subscriber.ack(subscriber3, events12)

        assert_receive {:events, ^subscriber1,
                        [%RecordedEvent{event_number: 13, stream_id: "stream0"}] = events13}

        assert_receive {:events, ^subscriber2,
                        [%RecordedEvent{event_number: 14, stream_id: "stream1"}] = events14}

        assert_receive {:events, ^subscriber3,
                        [%RecordedEvent{event_number: 15, stream_id: "stream2"}] = events15}

        refute_receive {:events, _subscriber, _received_events}

        :ok = Subscriber.ack(subscriber1, events13)
        :ok = Subscriber.ack(subscriber2, events14)
        :ok = Subscriber.ack(subscriber3, events15)

        refute_receive {:events, _subscriber, _received_events}
      end

      test "should exclude stopped subscriber from receiving events", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscriber1} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 2)

        {:ok, subscriber2} =
          Subscriber.start_link(event_store, event_store_meta, self(), concurrency_limit: 2)

        assert_receive {:subscribed, ^subscriber1, _subscription1}
        assert_receive {:subscribed, ^subscriber2, _subscription2}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(2))

        for n <- 1..2 do
          assert_receive {:events, subscriber, [%RecordedEvent{event_number: ^n}] = events}

          :ok = Subscriber.ack(subscriber, events)
        end

        stop_subscriber(subscriber1)

        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))

        for n <- 3..4 do
          assert_receive {:events, ^subscriber2, [%RecordedEvent{event_number: ^n}] = events}

          :ok = Subscriber.ack(subscriber2, events)
        end

        stop_subscriber(subscriber2)

        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(2))

        refute_receive {:events, _subscriber, _received_events}
      end
    end

    describe "unsubscribe from all streams" do
      test "should not receive further events appended to any stream", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))

        assert_receive_events(event_store, event_store_meta, subscription, count: 1, from: 1)

        :ok = unsubscribe(event_store, event_store_meta, subscription)

        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))
        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(3))

        refute_receive {:events, _received_events}
      end

      test "should resume subscription when subscribing again", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription1} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription1}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))

        assert_receive_events(event_store, event_store_meta, subscription1, count: 1, from: 1)

        :ok = unsubscribe(event_store, event_store_meta, subscription1)

        {:ok, subscription2} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))

        assert_receive {:subscribed, ^subscription2}
        assert_receive_events(event_store, event_store_meta, subscription2, count: 2, from: 2)
      end
    end

    describe "delete subscription" do
      test "should be deleted", %{event_store: event_store, event_store_meta: event_store_meta} do
        {:ok, subscription1} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription1}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))

        assert_receive_events(event_store, event_store_meta, subscription1, count: 1, from: 1)

        :ok = unsubscribe(event_store, event_store_meta, subscription1)

        assert :ok = event_store.delete_subscription(event_store_meta, :all, "subscriber")
      end

      test "should create new subscription after deletion", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscription1} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        assert_receive {:subscribed, ^subscription1}

        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))

        assert_receive_events(event_store, event_store_meta, subscription1, count: 1, from: 1)

        :ok = unsubscribe(event_store, event_store_meta, subscription1)

        :ok = event_store.delete_subscription(event_store_meta, :all, "subscriber")

        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(2))

        refute_receive {:events, _received_events}

        {:ok, subscription2} =
          event_store.subscribe_to(event_store_meta, :all, "subscriber", self(), :origin, [])

        # Should receive all events as subscription has been recreated from `:origin`
        assert_receive {:subscribed, ^subscription2}
        assert_receive_events(event_store, event_store_meta, subscription2, count: 1, from: 1)
        assert_receive_events(event_store, event_store_meta, subscription2, count: 2, from: 2)
      end
    end

    describe "resume subscription" do
      test "should resume from checkpoint", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(1))

        {:ok, subscriber1} = Subscriber.start_link(event_store, event_store_meta, self())

        assert_receive {:subscribed, ^subscriber1, _subscription}
        assert_receive {:events, ^subscriber1, received_events}
        assert length(received_events) == 1
        assert Enum.map(received_events, & &1.stream_id) == ["stream1"]

        :ok = Subscriber.ack(subscriber1, received_events)

        assert_receive {:events, ^subscriber1, received_events}
        assert length(received_events) == 1
        assert Enum.map(received_events, & &1.stream_id) == ["stream2"]

        :ok = Subscriber.ack(subscriber1, received_events)

        stop_subscriber(subscriber1)

        {:ok, subscriber2} = Subscriber.start_link(event_store, event_store_meta, self())

        assert_receive {:subscribed, ^subscriber2, _subscription}
        refute_receive {:events, _subscriber, _received_events}

        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(1))

        assert_receive {:events, ^subscriber2, received_events}
        assert length(received_events) == 1
        assert Enum.map(received_events, & &1.stream_id) == ["stream3"]

        :ok = Subscriber.ack(subscriber2, received_events)

        refute_receive {:events, _subscriber, _received_events}
      end

      test "should resume subscription from last successful ack", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        :ok = event_store.append_to_stream(event_store_meta, "stream1", 0, build_events(1))
        :ok = event_store.append_to_stream(event_store_meta, "stream2", 0, build_events(1))

        {:ok, subscriber1} = Subscriber.start_link(event_store, event_store_meta, self())

        assert_receive {:subscribed, ^subscriber1, _subscription}

        assert_receive {:events, ^subscriber1,
                        [%RecordedEvent{event_number: 1, stream_id: "stream1"}] = received_events}

        :ok = Subscriber.ack(subscriber1, received_events)

        assert_receive {:events, ^subscriber1,
                        [%RecordedEvent{event_number: 2, stream_id: "stream2"}]}

        stop_subscriber(subscriber1)

        {:ok, subscriber2} = Subscriber.start_link(event_store, event_store_meta, self())

        assert_receive {:subscribed, ^subscriber2, _subscription}

        # Receive event #2 again because it wasn't ack'd
        assert_receive {:events, ^subscriber2,
                        [%RecordedEvent{event_number: 2, stream_id: "stream2"}] = received_events}

        :ok = Subscriber.ack(subscriber2, received_events)

        :ok = event_store.append_to_stream(event_store_meta, "stream3", 0, build_events(1))

        assert_receive {:events, ^subscriber2,
                        [%RecordedEvent{event_number: 3, stream_id: "stream3"}] = received_events}

        :ok = Subscriber.ack(subscriber2, received_events)

        refute_receive {:events, _subscriber, _received_events}
      end
    end

    describe "subscription process" do
      test "should not stop subscriber process when subscription down", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscriber} = Subscriber.start_link(event_store, event_store_meta, self())

        ref = Process.monitor(subscriber)

        assert_receive {:subscribed, ^subscriber, subscription}

        ProcessHelper.shutdown(subscription)

        refute Process.alive?(subscription)
        assert Process.alive?(subscriber)
        refute_receive {:DOWN, ^ref, :process, ^subscriber, _reason}
      end

      test "should stop subscription process when subscriber down", %{
        event_store: event_store,
        event_store_meta: event_store_meta
      } do
        {:ok, subscriber} = Subscriber.start_link(event_store, event_store_meta, self())

        assert_receive {:subscribed, ^subscriber, subscription}

        ref = Process.monitor(subscription)

        stop_subscriber(subscriber)

        assert_receive {:DOWN, ^ref, :process, ^subscription, _reason}
      end
    end

    defp unsubscribe(event_store, event_store_meta, subscription) do
      :ok = event_store.unsubscribe(event_store_meta, subscription)

      wait_for_event_store()
    end

    defp stop_subscriber(subscriber) do
      ProcessHelper.shutdown(subscriber)

      wait_for_event_store()
    end

    # Optionally wait for the event store
    defp wait_for_event_store do
      case event_store_wait() do
        nil -> :ok
        wait -> :timer.sleep(wait)
      end
    end

    defp assert_receive_events(event_store, event_store_meta, subscription, opts) do
      opts = Keyword.put(opts, :subscription, subscription)

      assert_receive_events(event_store, event_store_meta, opts)
    end

    defp assert_receive_events(event_store, event_store_meta, opts) do
      expected_count = Keyword.fetch!(opts, :count)
      from_event_number = Keyword.get(opts, :from, 1)

      assert_receive {:events, received_events}
      assert_received_events(received_events, from_event_number)

      case Keyword.get(opts, :subscription) do
        subscription when is_pid(subscription) ->
          last_event = List.last(received_events)

          event_store.ack_event(event_store_meta, subscription, last_event)

        nil ->
          :ok
      end

      case expected_count - length(received_events) do
        0 ->
          received_events

        remaining when remaining > 0 ->
          opts =
            opts
            |> Keyword.put(:from, from_event_number + length(received_events))
            |> Keyword.put(:count, remaining)

          received_events ++ assert_receive_events(event_store, event_store_meta, opts)

        remaining when remaining < 0 ->
          flunk("Received #{abs(remaining)} more event(s) than expected")
      end
    end

    defp assert_received_events(received_events, from_event_number) do
      received_events
      |> Enum.with_index(from_event_number)
      |> Enum.each(fn {received_event, expected_event_number} ->
        assert received_event.event_number == expected_event_number
      end)
    end

    defp build_event(account_number) do
      %EventData{
        causation_id: UUID.uuid4(),
        correlation_id: UUID.uuid4(),
        event_type: "#{__MODULE__}.BankAccountOpened",
        data: %BankAccountOpened{account_number: account_number, initial_balance: 1_000},
        metadata: %{"user_id" => "test"}
      }
    end

    defp build_events(count) do
      for account_number <- 1..count, do: build_event(account_number)
    end
  end
end
