defmodule WhoNeedHelpWeb.SocialOAuthControllerTest do
  use WhoNeedHelpWeb.ConnCase, async: false

  alias WhoNeedHelp.{Accounts, Repo}
  alias WhoNeedHelp.Trust.AuditEvent

  setup :register_and_log_in_user

  test "requires an authenticated user" do
    conn = get(build_conn(), ~p"/auth/social/github")

    assert redirected_to(conn) == ~p"/users/log-in"
  end

  test "starts a user-bound authorization session", %{conn: conn, user: user} do
    conn = get(conn, ~p"/auth/social/github")

    assert redirected_to(conn) =~ "https://github.example/authorize?"

    assert %{
             "user_id" => user_id,
             "session_params" => %{state: "test-state", code_verifier: "test-verifier"}
           } = get_session(conn, "social_oauth_github")

    assert user_id == user.id
  end

  test "rejects unsupported providers", %{conn: conn} do
    conn = get(conn, "/auth/social/not-a-provider")

    assert redirected_to(conn) == ~p"/profile"
    assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "not supported"
  end

  test "links a verified identity without persisting the access token", %{
    conn: conn,
    user: user
  } do
    conn =
      conn
      |> get(~p"/auth/social/github")
      |> recycle()
      |> get(
        ~p"/auth/social/github/callback?code=one-time-code&state=test-state&uid=123&login=alice"
      )

    assert redirected_to(conn) == ~p"/profile"
    assert Phoenix.Flash.get(conn.assigns.flash, :info) == "GitHub profile verified."
    assert is_nil(get_session(conn, "social_oauth_github"))

    assert [identity] = Accounts.list_social_identities(user)
    assert identity.provider == :github
    assert identity.provider_uid == "123"
    assert identity.profile_url == "https://github.com/alice"
    assert identity.handle == "@alice"
    assert identity.verified_at == ~U[2026-07-18 18:00:00Z]

    refute Map.has_key?(Map.from_struct(identity), :access_token)
    refute inspect(identity) =~ "one-time-code"

    assert %AuditEvent{
             actor_id: actor_id,
             action: "social_identity.verified",
             target_id: target_id
           } =
             Repo.get_by(AuditEvent,
               action: "social_identity.verified",
               target_id: identity.id
             )

    assert actor_id == user.id
    assert target_id == identity.id
  end

  test "callback cannot be replayed", %{conn: conn} do
    conn =
      conn
      |> get(~p"/auth/social/github")
      |> recycle()
      |> get(~p"/auth/social/github/callback?code=code&state=test-state")

    replay =
      conn
      |> recycle()
      |> get(~p"/auth/social/github/callback?code=code&state=test-state")

    assert redirected_to(replay) == ~p"/profile"
    assert Phoenix.Flash.get(replay.assigns.flash, :error) =~ "session expired"
  end

  @tag :capture_log
  test "rejects a callback state mismatch and clears the flow session", %{conn: conn} do
    conn =
      conn
      |> get(~p"/auth/social/github")
      |> recycle()
      |> get(~p"/auth/social/github/callback?code=code&state=wrong")

    assert redirected_to(conn) == ~p"/profile"
    assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "failed"
    assert is_nil(get_session(conn, "social_oauth_github"))
  end

  test "rejects a flow bound to another local user", %{conn: conn} do
    other_user = WhoNeedHelp.AccountsFixtures.user_fixture()

    conn =
      conn
      |> put_session("social_oauth_github", %{
        "user_id" => other_user.id,
        "session_params" => %{state: "test-state", code_verifier: "test-verifier"}
      })
      |> get(~p"/auth/social/github/callback?code=code&state=test-state")

    assert redirected_to(conn) == ~p"/profile"
    assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "invalid"
    assert is_nil(get_session(conn, "social_oauth_github"))
  end

  test "does not let two users claim the same provider account", %{
    conn: first_conn,
    user: first_user
  } do
    first_conn
    |> get(~p"/auth/social/github")
    |> recycle()
    |> get(~p"/auth/social/github/callback?code=first&state=test-state&uid=shared")

    second_user = WhoNeedHelp.AccountsFixtures.user_fixture()

    second_conn =
      build_conn()
      |> log_in_user(second_user)
      |> get(~p"/auth/social/github")
      |> recycle()
      |> get(~p"/auth/social/github/callback?code=second&state=test-state&uid=shared")

    assert redirected_to(second_conn) == ~p"/profile"
    assert Phoenix.Flash.get(second_conn.assigns.flash, :error) =~ "already linked"
    assert [_identity] = Accounts.list_social_identities(first_user)
    assert [] == Accounts.list_social_identities(second_user)
  end
end
