defmodule MvWeb.OidcIntegrationTest do
  use MvWeb.ConnCase, async: true

  # Test OIDC callback scenarios by directly calling the actions
  # This simulates what happens during real OIDC authentication

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

  describe "OIDC sign-in scenarios" do
    test "existing OIDC user with unchanged email can sign in" do
      # Create user with OIDC ID
      user =
        create_test_user(%{
          email: "existing@example.com",
          oidc_id: "existing_oidc_123"
        })

      # Simulate OIDC callback data
      user_info = %{
        "sub" => "existing_oidc_123",
        "preferred_username" => "existing@example.com"
      }

      # Test sign_in_with_oidc action directly
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      result =
        Mv.Accounts.read_sign_in_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      found_user =
        case result do
          {:ok, u} when is_struct(u) -> u
          {:ok, [u]} -> u
          _ -> flunk("Expected user, got: #{inspect(result)}")
        end

      assert found_user.id == user.id
      assert to_string(found_user.email) == "existing@example.com"
      assert found_user.oidc_id == "existing_oidc_123"
    end

    test "new OIDC user gets created via register_with_oidc" do
      # Simulate OIDC callback for completely new user
      user_info = %{
        "sub" => "brand_new_oidc_456",
        "preferred_username" => "newuser@example.com"
      }

      # Test register_with_oidc action
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      case Mv.Accounts.create_register_with_oidc(
             %{
               user_info: user_info,
               oauth_tokens: %{}
             },
             actor: system_actor
           ) do
        {:ok, new_user} ->
          assert to_string(new_user.email) == "newuser@example.com"
          assert new_user.oidc_id == "brand_new_oidc_456"
          assert is_nil(new_user.hashed_password)

        {:error, error} ->
          flunk("Should have created new user: #{inspect(error)}")
      end
    end
  end

  describe "OIDC sign-in security tests" do
    @tag :test_proposal
    test "sign_in_with_oidc does NOT match user with only email (no oidc_id)" do
      # SECURITY TEST: Ensure password-only users cannot be accessed via OIDC
      # Create a password-only user (no oidc_id)
      _password_user =
        create_test_user(%{
          email: "password.only@example.com",
          password: "securepassword123",
          oidc_id: nil
        })

      # Try to sign in with OIDC using the same email
      user_info = %{
        "sub" => "attacker_oidc_456",
        "preferred_username" => "password.only@example.com"
      }

      # Should NOT find any user (security requirement)
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      result =
        Mv.Accounts.read_sign_in_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      # Either returns empty list OR authentication error - both mean "user not found"
      case result do
        {:ok, []} ->
          :ok

        {:ok, nil} ->
          :ok

        {:error, %Ash.Error.Forbidden{errors: [%AshAuthentication.Errors.AuthenticationFailed{}]}} ->
          :ok

        other ->
          flunk("Expected no user match, got: #{inspect(other)}")
      end
    end

    @tag :test_proposal
    test "sign_in_with_oidc only matches when oidc_id matches" do
      # Create user with specific OIDC ID
      user =
        create_test_user(%{
          email: "oidc.user@example.com",
          oidc_id: "correct_oidc_789"
        })

      # Try with correct oidc_id
      correct_user_info = %{
        "sub" => "correct_oidc_789",
        "preferred_username" => "oidc.user@example.com"
      }

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      result =
        Mv.Accounts.read_sign_in_with_oidc(
          %{
            user_info: correct_user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      found_user =
        case result do
          {:ok, u} when is_struct(u) -> u
          {:ok, [u]} -> u
          _ -> flunk("Expected user, got: #{inspect(result)}")
        end

      assert found_user.id == user.id

      # Try with wrong oidc_id but correct email
      wrong_user_info = %{
        "sub" => "wrong_oidc_999",
        "preferred_username" => "oidc.user@example.com"
      }

      result =
        Mv.Accounts.read_sign_in_with_oidc(
          %{
            user_info: wrong_user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      # Either returns empty/nil OR authentication error - both mean "user not found"
      case result do
        {:ok, []} ->
          :ok

        {:ok, nil} ->
          :ok

        {:error, %Ash.Error.Forbidden{errors: [%AshAuthentication.Errors.AuthenticationFailed{}]}} ->
          :ok

        other ->
          flunk("Expected no user match when oidc_id differs, got: #{inspect(other)}")
      end
    end

    @tag :test_proposal
    test "sign_in_with_oidc does not match user with empty string oidc_id" do
      # Edge case: empty string should be treated like nil
      _user =
        create_test_user(%{
          email: "empty.oidc@example.com",
          oidc_id: ""
        })

      user_info = %{
        "sub" => "new_oidc_111",
        "preferred_username" => "empty.oidc@example.com"
      }

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      result =
        Mv.Accounts.read_sign_in_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      # Either returns empty/nil OR authentication error - both mean "user not found"
      case result do
        {:ok, []} ->
          :ok

        {:ok, nil} ->
          :ok

        {:error, %Ash.Error.Forbidden{errors: [%AshAuthentication.Errors.AuthenticationFailed{}]}} ->
          :ok

        other ->
          flunk("Expected no user match with empty oidc_id, got: #{inspect(other)}")
      end
    end
  end

  describe "OIDC error and edge case scenarios" do
    test "OIDC registration with conflicting email and OIDC ID shows hard error" do
      # Create user with email and OIDC ID
      _existing_user =
        create_test_user(%{
          email: "conflict@example.com",
          oidc_id: "oidc_conflict_1"
        })

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

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      result =
        Mv.Accounts.create_register_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      # Should fail with hard error (not PasswordVerificationRequired)
      # This prevents someone with OIDC provider B from taking over an account
      # that's already linked to OIDC provider A
      assert {:error, %Ash.Error.Invalid{errors: errors}} = result

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

               _ ->
                 false
             end)

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

    test "OIDC registration with missing sub and id should fail" do
      user_info = %{
        "preferred_username" => "nosub@example.com"
      }

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      result =
        Mv.Accounts.create_register_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      assert {:error,
              %Ash.Error.Invalid{
                errors: [%Ash.Error.Changes.InvalidChanges{vars: [user_info: msg]}]
              }} = result

      assert String.contains?(msg, "OIDC user_info must contain a non-empty 'sub' or 'id' field")
    end

    test "OIDC registration with missing preferred_username should fail" do
      user_info = %{
        "sub" => "noemail_oidc_123"
      }

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      result =
        Mv.Accounts.create_register_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      assert {:error, %Ash.Error.Invalid{errors: errors}} = result

      assert Enum.any?(errors, fn err ->
               match?(%Ash.Error.Changes.Required{field: :email}, err)
             end)
    end

    test "OIDC registration with existing OIDC ID and different email updates email" do
      existing_user =
        create_test_user(%{
          email: "old@example.com",
          oidc_id: "oidc_update_email"
        })

      user_info = %{
        "sub" => "oidc_update_email",
        "preferred_username" => "new@example.com"
      }

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      {:ok, user} =
        Mv.Accounts.create_register_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      assert user.id == existing_user.id
      assert to_string(user.email) == "new@example.com"
      assert user.oidc_id == "oidc_update_email"
    end

    test "OIDC registration with alternative OIDC ID field (id instead of sub)" do
      user_info = %{
        "id" => "alt_oidc_id_123",
        "preferred_username" => "altid@example.com"
      }

      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      {:ok, user} =
        Mv.Accounts.create_register_with_oidc(
          %{
            user_info: user_info,
            oauth_tokens: %{}
          },
          actor: system_actor
        )

      assert user.oidc_id == "alt_oidc_id_123"
      assert to_string(user.email) == "altid@example.com"
    end
  end
end
