defmodule MatchmakingEx.PendingMatchTest do
  @moduledoc false
  use ExUnit.Case, async: false

  alias MatchmakingEx.PendingMatch
  alias MatchmakingEx.Server
  alias MatchmakingEx.CooldownManager

  # Sets up the test environment for each test case.
  #
  # This setup function performs the following actions:
  # 1. Starts the `Server` and `CooldownManager` processes to ensure a clean state.
  # 2. Creates two mock player structs (`player1` and `player2`) that will be used
  #    to initiate a `PendingMatch`. The test process itself (`self()`) is used
  #    as the `pid` for these players to receive messages.
  setup do
    start_supervised!(Server)
    start_supervised!(CooldownManager)

    player1 = %Server{pid: self(), id: "player1", filters: %{}, inserted_at: nil}
    player2 = %Server{pid: self(), id: "player2", filters: %{}, inserted_at: nil}
    {:ok, player1: player1, player2: player2}
  end

  @doc """
  Tests the "happy path" where a match is successfully confirmed.
  """
  test "starts, confirms, and terminates when both players accept", %{player1: p1, player2: p2} do
    {:ok, child_pid} = PendingMatch.create(p1, p2)
    ref = Process.monitor(child_pid)

    assert_receive {:match_ready_for_confirmation, match_id, _}
    assert_receive {:match_ready_for_confirmation, ^match_id, _}

    PendingMatch.accept(match_id, "player1")
    PendingMatch.accept(match_id, "player2")

    assert_receive {:match_confirmed, ^match_id}
    assert_receive {:match_confirmed, ^match_id}
    assert_receive {:DOWN, ^ref, :process, _pid, :normal}
  end

  @doc """
  Tests the scenario where one player accepts and the other declines.
  It verifies that the declining player is put on cooldown and the accepting player is re-queued.
  """
  test "cancels, re-queues the accepting player, and penalizes the declining one", %{
    player1: p1,
    player2: p2
  } do
    {:ok, child_pid} = PendingMatch.create(p1, p2)
    ref = Process.monitor(child_pid)
    assert_receive {:match_ready_for_confirmation, match_id, _}
    assert_receive {:match_ready_for_confirmation, ^match_id, _}

    # P1 accepts, P2 declines.
    PendingMatch.accept(match_id, "player1")
    :ok = PendingMatch.decline(match_id, "player2")

    assert_receive {:match_cancelled, "Opponent has declined."}
    assert_receive {:match_cancelled, "Opponent has declined."}
    assert_receive {:DOWN, ^ref, :process, _pid, :normal}

    # Verify that P2 (who declined) is on cooldown.
    assert {:ok, true, _expiration} = CooldownManager.is_on_cooldown?("player2")

    # Verify that P1 (who accepted) was re-queued by trying to find them.
    # We use a new "finder" player to check for p1's presence in the queue.
    finder_player = %Server{pid: self(), id: "finder", filters: %{}, inserted_at: nil}
    Server.find_match(finder_player.pid, finder_player.id, finder_player.filters)

    assert_receive {:match_ready_for_confirmation, _new_match_id, ["finder", "player1"]}
  end

  @doc """
  Tests the scenario where a match times out before both players can accept.
  """
  test "cancels and re-queues accepting players after a timeout", %{player1: p1, player2: p2} do
    {:ok, child_pid} = PendingMatch.create(p1, p2, 10)
    ref = Process.monitor(child_pid)
    assert_receive {:match_ready_for_confirmation, match_id, _}
    assert_receive {:match_ready_for_confirmation, ^match_id, _}

    PendingMatch.accept(match_id, "player1")
    # Wait for the timeout to occur.
    :timer.sleep(20)

    assert_receive {:match_cancelled, "Match timed out."}
    assert_receive {:match_cancelled, "Match timed out."}
    assert_receive {:DOWN, ^ref, :process, _pid, :normal}

    # Verify that the accepting player (p1) is not on cooldown.
    assert {:ok, false} = CooldownManager.is_on_cooldown?("player1")
  end

  @doc """
  Tests that the player who declines is put on cooldown.
  """
  test "puts the declining player on cooldown", %{player1: p1, player2: p2} do
    {:ok, _} = PendingMatch.create(p1, p2)
    assert_receive {:match_ready_for_confirmation, match_id, _}
    assert_receive {:match_ready_for_confirmation, ^match_id, _}

    :ok = PendingMatch.decline(match_id, "player1")

    # Verify that player1 is on cooldown.
    assert {:ok, true, _expiration} = CooldownManager.is_on_cooldown?("player1")
    # Verify that player2 is not on cooldown.
    assert {:ok, false} = CooldownManager.is_on_cooldown?("player2")
  end
end
