defmodule MvWeb.OidcPasswordlessLinkingTest do
  @moduledoc """
  Tests for OIDC account linking with passwordless users.

  These tests verify the behavior when a passwordless user
  (e.g., invited user, user created by admin) attempts to log in via OIDC.
  """
  use MvWeb.ConnCase, async: true

  setup do
    system_actor = Mv.Helpers.SystemActor.get_system_actor()
    %{actor: system_actor}
  end

  describe "Passwordless user - Automatic linking via special action" do
    test "passwordless user can be linked via link_passwordless_oidc action", %{actor: actor} do
      # Create user without password (e.g., invited user)
      {:ok, existing_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "invited@example.com"
        })
        |> Ash.create(actor: actor)

      # Verify user has no password and no oidc_id
      assert is_nil(existing_user.hashed_password)
      assert is_nil(existing_user.oidc_id)

      # Link via special action (simulating what happens after first OIDC attempt)
      {:ok, linked_user} =
        existing_user
        |> Ash.Changeset.for_update(:link_oidc_id, %{
          oidc_id: "auto_link_oidc_123",
          oidc_user_info: %{
            "sub" => "auto_link_oidc_123",
            "preferred_username" => "invited@example.com"
          }
        })
        |> Ash.update(actor: actor)

      # User should now have oidc_id linked
      assert linked_user.oidc_id == "auto_link_oidc_123"
      assert linked_user.id == existing_user.id

      # Now OIDC sign-in should work
      result =
        Mv.Accounts.User
        |> Ash.Query.for_read(:sign_in_with_oidc, %{
          user_info: %{
            "sub" => "auto_link_oidc_123",
            "preferred_username" => "invited@example.com"
          },
          oauth_tokens: %{"access_token" => "test_token"}
        })
        |> Ash.read_one(actor: actor)

      assert {:ok, signed_in_user} = result
      assert signed_in_user.id == existing_user.id
    end

    test "passwordless user triggers PasswordVerificationRequired for linking flow", %{
      actor: actor
    } do
      # Create passwordless user
      {:ok, existing_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "passwordless@example.com"
        })
        |> Ash.create(actor: actor)

      assert is_nil(existing_user.hashed_password)
      assert is_nil(existing_user.oidc_id)

      # Try OIDC registration - should trigger PasswordVerificationRequired
      user_info = %{
        "sub" => "new_oidc_456",
        "preferred_username" => "passwordless@example.com"
      }

      result =
        Mv.Accounts.create_register_with_oidc(%{
          user_info: user_info,
          oauth_tokens: %{"access_token" => "test_token"}
        })

      # Should fail with PasswordVerificationRequired
      # LinkOidcAccountLive will auto-link without password prompt
      assert {:error, %Ash.Error.Invalid{}} = result
      {:error, error} = result

      assert Enum.any?(error.errors, fn err ->
               case err do
                 %Mv.Accounts.User.Errors.PasswordVerificationRequired{user_id: user_id} ->
                   user_id == existing_user.id

                 _ ->
                   false
               end
             end)
    end
  end

  describe "User with different OIDC ID - Hard Error" do
    test "user with different oidc_id gets hard error, not password verification", %{actor: actor} do
      # Create user with existing OIDC ID
      {:ok, _existing_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "already-linked@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "original_oidc_999")
        |> Ash.create(actor: actor)

      # Try to register with same email but different OIDC ID
      user_info = %{
        "sub" => "different_oidc_888",
        "preferred_username" => "already-linked@example.com"
      }

      result =
        Mv.Accounts.create_register_with_oidc(%{
          user_info: user_info,
          oauth_tokens: %{"access_token" => "test_token"}
        })

      # Should fail with hard error
      assert {:error, %Ash.Error.Invalid{}} = result
      {:error, error} = result

      # Should NOT be PasswordVerificationRequired
      refute Enum.any?(error.errors, fn err ->
               match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
             end)

      # Should have error message about already linked
      assert Enum.any?(error.errors, fn err ->
               case err do
                 %Ash.Error.Changes.InvalidAttribute{message: msg} ->
                   String.contains?(msg, "already linked to a different OIDC account")

                 _ ->
                   false
               end
             end)
    end

    test "passwordless user with different oidc_id also gets hard error", %{actor: actor} do
      # Create passwordless user with OIDC ID
      {:ok, existing_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "passwordless-linked@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "first_oidc_777")
        |> Ash.create(actor: actor)

      assert is_nil(existing_user.hashed_password)
      assert existing_user.oidc_id == "first_oidc_777"

      # Try to register with different OIDC ID
      user_info = %{
        "sub" => "second_oidc_666",
        "preferred_username" => "passwordless-linked@example.com"
      }

      result =
        Mv.Accounts.create_register_with_oidc(%{
          user_info: user_info,
          oauth_tokens: %{"access_token" => "test_token"}
        })

      # Should be hard error, not PasswordVerificationRequired
      assert {:error, %Ash.Error.Invalid{}} = result
      {:error, error} = result

      refute Enum.any?(error.errors, fn err ->
               match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
             end)
    end
  end

  describe "Password user - Requires verification (existing behavior)" do
    test "password user without oidc_id requires password verification" do
      # Create password user
      password_user =
        create_test_user(%{
          email: "password@example.com",
          password: "securepass123",
          oidc_id: nil
        })

      assert not is_nil(password_user.hashed_password)
      assert is_nil(password_user.oidc_id)

      # Try OIDC registration
      user_info = %{
        "sub" => "new_oidc_999",
        "preferred_username" => "password@example.com"
      }

      result =
        Mv.Accounts.create_register_with_oidc(%{
          user_info: user_info,
          oauth_tokens: %{"access_token" => "test_token"}
        })

      # Should require password verification
      assert {:error, %Ash.Error.Invalid{}} = result
      {:error, error} = result

      assert Enum.any?(error.errors, fn err ->
               match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
             end)
    end
  end
end
