defmodule Parrhesia.API.EventsTest do
  use Parrhesia.IntegrationCase, async: false, sandbox: true

  alias Parrhesia.API.Events
  alias Parrhesia.API.RequestContext
  alias Parrhesia.Protocol.EventValidator

  test "publish stores valid events through the shared API" do
    event = valid_event()

    assert {:ok, result} = Events.publish(event, context: %RequestContext{})
    assert result.accepted
    assert result.event_id == event["id"]
    assert result.message == "ok: event stored"
    assert result.reason == nil

    assert {:ok, stored_event} = Parrhesia.Storage.events().get_event(%{}, event["id"])
    assert stored_event["id"] == event["id"]
  end

  test "publish returns duplicate results without raising transport errors" do
    event = valid_event()

    assert {:ok, first_result} = Events.publish(event, context: %RequestContext{})
    assert first_result.accepted

    assert {:ok, second_result} = Events.publish(event, context: %RequestContext{})
    refute second_result.accepted
    assert second_result.reason == :duplicate_event
    assert second_result.message == "duplicate: event already stored"
  end

  test "publish fanout includes sync-originated events when relay guard is disabled" do
    with_sync_relay_guard(false)
    join_multi_node_group!()

    event = valid_event()
    event_id = event["id"]

    assert {:ok, %{accepted: true}} =
             Events.publish(event, context: %RequestContext{caller: :sync})

    assert_receive {:remote_fanout_event, %{"id" => ^event_id}}, 200
  end

  test "publish fanout skips sync-originated events when relay guard is enabled" do
    with_sync_relay_guard(true)
    join_multi_node_group!()

    event = valid_event()
    event_id = event["id"]

    assert {:ok, %{accepted: true}} =
             Events.publish(event, context: %RequestContext{caller: :sync})

    refute_receive {:remote_fanout_event, %{"id" => ^event_id}}, 200
  end

  test "publish fanout still includes local-originated events when relay guard is enabled" do
    with_sync_relay_guard(true)
    join_multi_node_group!()

    event = valid_event()
    event_id = event["id"]

    assert {:ok, %{accepted: true}} =
             Events.publish(event, context: %RequestContext{caller: :local})

    assert_receive {:remote_fanout_event, %{"id" => ^event_id}}, 200
  end

  test "query and count preserve read semantics through the shared API" do
    now = System.system_time(:second)
    first = valid_event(%{"content" => "first", "created_at" => now})
    second = valid_event(%{"content" => "second", "created_at" => now + 1})

    assert {:ok, %{accepted: true}} = Events.publish(first, context: %RequestContext{})
    assert {:ok, %{accepted: true}} = Events.publish(second, context: %RequestContext{})

    assert {:ok, events} =
             Events.query([%{"kinds" => [1]}], context: %RequestContext{})

    assert Enum.map(events, & &1["id"]) == [second["id"], first["id"]]

    assert {:ok, 2} =
             Events.count([%{"kinds" => [1]}], context: %RequestContext{})

    assert {:ok, %{"count" => 2, "approximate" => false}} =
             Events.count([%{"kinds" => [1]}],
               context: %RequestContext{},
               options: %{}
             )
  end

  test "local query can read protected sync events without ACL grants or kind scoping" do
    previous_acl = Application.get_env(:parrhesia, :acl, [])

    Application.put_env(
      :parrhesia,
      :acl,
      protected_filters: [%{"kinds" => [5000], "#r" => ["tribes.accounts.user"]}]
    )

    on_exit(fn ->
      Application.put_env(:parrhesia, :acl, previous_acl)
    end)

    protected_event =
      valid_event(%{
        "kind" => 5000,
        "tags" => [["r", "tribes.accounts.user"]],
        "content" => "protected"
      })

    assert {:ok, %{accepted: true}} =
             Events.publish(protected_event, context: %RequestContext{caller: :local})

    assert {:ok, [stored_event]} =
             Events.query([%{"ids" => [protected_event["id"]]}],
               context: %RequestContext{caller: :local}
             )

    assert stored_event["id"] == protected_event["id"]
  end

  defp with_sync_relay_guard(enabled?) when is_boolean(enabled?) do
    [{:config, previous}] = :ets.lookup(Parrhesia.Config, :config)

    sync =
      previous
      |> Map.get(:sync, [])
      |> Keyword.put(:relay_guard, enabled?)

    :ets.insert(Parrhesia.Config, {:config, Map.put(previous, :sync, sync)})

    on_exit(fn ->
      :ets.insert(Parrhesia.Config, {:config, previous})
    end)
  end

  defp join_multi_node_group! do
    case Process.whereis(:pg) do
      nil ->
        case :pg.start_link() do
          {:ok, _pid} -> :ok
          {:error, {:already_started, _pid}} -> :ok
        end

      _pid ->
        :ok
    end

    :ok = :pg.join(Parrhesia.Fanout.MultiNode, self())
  end

  defp valid_event(overrides \\ %{}) do
    base_event = %{
      "pubkey" => String.duplicate("1", 64),
      "created_at" => System.system_time(:second),
      "kind" => 1,
      "tags" => [],
      "content" => "hello",
      "sig" => String.duplicate("3", 128)
    }

    base_event
    |> Map.merge(overrides)
    |> recalculate_event_id()
  end

  defp recalculate_event_id(event) do
    Map.put(event, "id", EventValidator.compute_id(event))
  end
end
