defmodule MvWeb.OidcEmailUpdateTest do
  @moduledoc """
  Tests for OIDC email updates - when an existing OIDC user changes their email
  in the OIDC provider and logs in again.
  """
  use MvWeb.ConnCase, async: true

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

  describe "OIDC user updates email to available email" do
    test "should succeed and update email", %{actor: actor} do
      # Create OIDC user
      {:ok, oidc_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "original@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_123")
        |> Ash.create(actor: actor)

      # User logs in via OIDC with NEW email
      user_info = %{
        "sub" => "oidc_123",
        "preferred_username" => "newemail@example.com"
      }

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

      # Should succeed and email should be updated
      assert {:ok, updated_user} = result
      assert updated_user.id == oidc_user.id
      assert to_string(updated_user.email) == "newemail@example.com"
      assert updated_user.oidc_id == "oidc_123"
    end
  end

  describe "OIDC user updates email to email of passwordless user" do
    test "should fail with clear error message", %{actor: actor} do
      # Create OIDC user
      {:ok, _oidc_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "oidcuser@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_456")
        |> Ash.create(actor: actor)

      # Create passwordless user with target email
      {:ok, _passwordless_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "taken@example.com"
        })
        |> Ash.create(actor: actor)

      # OIDC user tries to update email to taken email
      user_info = %{
        "sub" => "oidc_456",
        "preferred_username" => "taken@example.com"
      }

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

      # Should fail with email update conflict error
      assert {:error, %Ash.Error.Invalid{errors: errors}} = result

      # Should contain error about email being registered to another account
      assert Enum.any?(errors, fn
               %Ash.Error.Changes.InvalidAttribute{field: :email, message: message} ->
                 String.contains?(message, "Cannot update email to") and
                   String.contains?(message, "already registered to another account")

               _ ->
                 false
             end)

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

  describe "OIDC user updates email to email of password-protected user" do
    test "should fail with clear error message", %{actor: actor} do
      # Create OIDC user
      {:ok, _oidc_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "oidcuser2@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_789")
        |> Ash.create(actor: actor)

      # Create password user with target email (explicitly NO oidc_id)
      password_user =
        create_test_user(%{
          email: "passworduser@example.com",
          password: "securepass123"
        })

      # Ensure it's a password-only user
      {:ok, password_user} = Ash.reload(password_user, actor: actor)
      assert not is_nil(password_user.hashed_password)
      # Force oidc_id to be nil to avoid any confusion
      {:ok, password_user} =
        password_user
        |> Ash.Changeset.for_update(:update, %{})
        |> Ash.Changeset.force_change_attribute(:oidc_id, nil)
        |> Ash.update(actor: actor)

      assert is_nil(password_user.oidc_id)

      # OIDC user tries to update email to password user's email
      user_info = %{
        "sub" => "oidc_789",
        "preferred_username" => "passworduser@example.com"
      }

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

      # Should fail with email update conflict error
      assert {:error, %Ash.Error.Invalid{errors: errors}} = result

      # Should contain error about email being registered to another account
      assert Enum.any?(errors, fn
               %Ash.Error.Changes.InvalidAttribute{field: :email, message: message} ->
                 String.contains?(message, "Cannot update email to") and
                   String.contains?(message, "already registered to another account")

               _ ->
                 false
             end)

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

  describe "OIDC user updates email to email of different OIDC user" do
    test "should fail with clear error message about different OIDC account", %{actor: actor} do
      # Create first OIDC user
      {:ok, _oidc_user1} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "oidcuser1@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_aaa")
        |> Ash.create(actor: actor)

      # Create second OIDC user with target email
      {:ok, _oidc_user2} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "oidcuser2@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_bbb")
        |> Ash.create(actor: actor)

      # First OIDC user tries to update email to second user's email
      user_info = %{
        "sub" => "oidc_aaa",
        "preferred_username" => "oidcuser2@example.com"
      }

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

      # Should fail with "already linked to different OIDC account" error
      assert {:error, %Ash.Error.Invalid{errors: errors}} = result

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

               _ ->
                 false
             end)

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

  describe "New OIDC user registration scenarios (for comparison)" do
    test "new OIDC user with email of passwordless user triggers linking flow", %{actor: actor} do
      # Create passwordless user
      {:ok, passwordless_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "passwordless@example.com"
        })
        |> Ash.create(actor: actor)

      # New OIDC user tries to register
      user_info = %{
        "sub" => "new_oidc_999",
        "preferred_username" => "passwordless@example.com"
      }

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

      # Should trigger PasswordVerificationRequired (linking flow)
      assert {:error, %Ash.Error.Invalid{errors: errors}} = result

      assert Enum.any?(errors, fn
               %Mv.Accounts.User.Errors.PasswordVerificationRequired{user_id: user_id} ->
                 user_id == passwordless_user.id

               _ ->
                 false
             end)
    end

    test "new OIDC user with email of existing OIDC user shows hard error", %{actor: actor} do
      # Create existing OIDC user
      {:ok, _existing_oidc_user} =
        Mv.Accounts.User
        |> Ash.Changeset.for_create(:create_user, %{
          email: "existing@example.com"
        })
        |> Ash.Changeset.force_change_attribute(:oidc_id, "oidc_existing")
        |> Ash.create(actor: actor)

      # New OIDC user tries to register with same email
      user_info = %{
        "sub" => "oidc_new",
        "preferred_username" => "existing@example.com"
      }

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

      # Should fail with "already linked to different OIDC account" error
      assert {:error, %Ash.Error.Invalid{errors: errors}} = result

      assert Enum.any?(errors, fn
               %Ash.Error.Changes.InvalidAttribute{field: :email, message: message} ->
                 String.contains?(message, "already linked to a different OIDC account")

               _ ->
                 false
             end)
    end
  end
end
