defmodule Microwaveprop.RepoListenerTest do
  @moduledoc """
  Characterization tests for `Microwaveprop.RepoListener`.

  The module is a GenServer that subscribes to two PostgreSQL `NOTIFY`
  channels via `Postgrex.Notifications` and rebroadcasts decoded
  payloads via `Phoenix.PubSub`. These tests exercise the `handle_info/2`
  seam directly using the exact message shape `Postgrex.Notifications`
  delivers — no database connection required.
  """

  use ExUnit.Case, async: false

  alias Microwaveprop.RepoListener

  # Shape of the message that `Postgrex.Notifications` delivers to a
  # listening process. Ref. https://hexdocs.pm/postgrex/Postgrex.Notifications.html
  defp notification(channel, payload) do
    {:notification, self(), make_ref(), channel, payload}
  end

  describe "handle_info/2 — contact_status_changed" do
    setup do
      :ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
      on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:contact_status") end)
      :ok
    end

    test "broadcasts decoded payload on db:contact_status topic" do
      payload = Jason.encode!(%{"contact_id" => "abc-123", "status" => "queued"})
      msg = notification("contact_status_changed", payload)

      assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})

      assert_receive {:contact_status_changed, %{"contact_id" => "abc-123", "status" => "queued"}}
    end

    test "preserves full decoded payload structure (nested maps/lists)" do
      data = %{
        "contact_id" => "xyz",
        "fields" => ["weather", "hrrr"],
        "meta" => %{"source" => "notify"}
      }

      msg = notification("contact_status_changed", Jason.encode!(data))

      assert {:noreply, _} = RepoListener.handle_info(msg, %{pid: nil})
      assert_receive {:contact_status_changed, ^data}
    end

    test "silently ignores invalid JSON payload" do
      msg = notification("contact_status_changed", "not-json-at-all{")

      assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
      refute_receive {:contact_status_changed, _}, 50
    end
  end

  describe "handle_info/2 — oban_job_changed" do
    setup do
      :ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
      on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:oban_jobs") end)
      :ok
    end

    test "broadcasts decoded payload on db:oban_jobs topic" do
      payload = Jason.encode!(%{"id" => 42, "state" => "completed", "queue" => "weather"})
      msg = notification("oban_job_changed", payload)

      assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})

      assert_receive {:oban_job_changed, %{"id" => 42, "state" => "completed", "queue" => "weather"}}
    end

    test "silently ignores invalid JSON payload" do
      msg = notification("oban_job_changed", "{\"broken\":")

      assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
      refute_receive {:oban_job_changed, _}, 50
    end
  end

  describe "handle_info/2 — channel isolation" do
    test "contact_status_changed does not broadcast on db:oban_jobs" do
      :ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
      on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:oban_jobs") end)

      msg = notification("contact_status_changed", Jason.encode!(%{"contact_id" => "abc"}))

      assert {:noreply, _} = RepoListener.handle_info(msg, %{pid: nil})
      refute_receive {:oban_job_changed, _}, 50
      refute_receive {:contact_status_changed, _}, 50
    end

    test "oban_job_changed does not broadcast on db:contact_status" do
      :ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
      on_exit(fn -> Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:contact_status") end)

      msg = notification("oban_job_changed", Jason.encode!(%{"id" => 1}))

      assert {:noreply, _} = RepoListener.handle_info(msg, %{pid: nil})
      refute_receive {:contact_status_changed, _}, 50
      refute_receive {:oban_job_changed, _}, 50
    end
  end

  describe "handle_info/2 — unknown / unrelated messages" do
    test "ignores notifications on unknown channels without crashing" do
      :ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
      :ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")

      on_exit(fn ->
        Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:contact_status")
        Phoenix.PubSub.unsubscribe(Microwaveprop.PubSub, "db:oban_jobs")
      end)

      msg = notification("some_other_channel", Jason.encode!(%{"x" => 1}))

      assert {:noreply, %{pid: nil}} = RepoListener.handle_info(msg, %{pid: nil})
      refute_receive {:contact_status_changed, _}, 50
      refute_receive {:oban_job_changed, _}, 50
    end

    test "ignores arbitrary info messages without crashing" do
      assert {:noreply, %{pid: nil}} = RepoListener.handle_info(:random_message, %{pid: nil})
      assert {:noreply, %{pid: nil}} = RepoListener.handle_info({:foo, :bar}, %{pid: nil})
    end

    test "returns state unchanged regardless of message" do
      state = %{pid: nil, extra: :keep_me}
      assert {:noreply, ^state} = RepoListener.handle_info(:whatever, state)

      msg = notification("contact_status_changed", Jason.encode!(%{"a" => 1}))
      assert {:noreply, ^state} = RepoListener.handle_info(msg, state)
    end
  end
end
