defmodule Mobilizon.GraphQL.Resolvers.UserTest do
  use Mobilizon.Web.ConnCase
  use Oban.Testing, repo: Mobilizon.Storage.Repo

  import Mobilizon.Factory

  alias Mobilizon.{Actors, Config, Discussions, Events, Users}
  alias Mobilizon.Actors.Actor
  alias Mobilizon.Discussions.Comment
  alias Mobilizon.Events.{Event, Participant}
  alias Mobilizon.Service.Auth.Authenticator
  alias Mobilizon.Users.User

  alias Mobilizon.GraphQL.AbsintheHelpers

  alias Mobilizon.Web.Email
  import Swoosh.TestAssertions

  @get_user_query """
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      email
    }
  }
  """

  @logged_user_query """
  query LoggedUser {
    loggedUser {
      id
      email
    }
  }
  """

  @list_users_query """
  query ListUsers(
    $email: String
    $currentSignInIp: String
    $page: Int
    $limit: Int
    $sort: SortableUserField
    $direction: SortDirection
  ) {
    users(
      email: $email
      currentSignInIp: $currentSignInIp
      page: $page
      limit: $limit
      sort: $sort
      direction: $direction
    ) {
      total,
      elements {
        email
      }
    }
  }
  """

  @create_user_mutation """
  mutation CreateUser($email: String!, $password: String!, $locale: String) {
    createUser(
      email: $email
      password: $password
      locale: $locale
  ) {
      id,
      email,
      locale
    }
  }
  """

  @register_person_mutation """
  mutation RegisterPerson($preferredUsername: String!, $name: String, $summary: String, $email: String!) {
    registerPerson(
      preferredUsername: $preferredUsername,
      name: $name,
      summary: $summary,
      email: $email,
      ) {
        preferredUsername,
        name,
        summary,
        avatar {
          url
        },
      }
    }
  """

  @change_email_mutation """
      mutation ChangeEmail($email: String!, $password: String!) {
        changeEmail(email: $email, password: $password) {
            id
          }
        }
  """

  @login_mutation """
      mutation Login($email: String!, $password: String!) {
        login(email: $email, password: $password) {
            accessToken,
            refreshToken,
            user {
              id
            }
          }
        }
  """

  @validate_email_mutation """
      mutation ValidateEmail($token: String!) {
        validateEmail(
              token: $token
          ) {
            id
          }
        }
  """

  @send_reset_password_mutation """
    mutation SendResetPassword($email: String!) {
      sendResetPassword(email: $email)
    }
  """

  @delete_user_account_mutation """
    mutation DeleteAccount($password: String) {
      deleteAccount (password: $password) {
        id
      }
    }
  """

  @change_default_actor """
  mutation ChangeDefaultActor($preferredUsername: String!) {
    changeDefaultActor(preferredUsername: $preferredUsername) {
        defaultActor {
          preferredUsername
        }
      }
    }
  """

  @valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"}
  @valid_single_actor_params %{preferred_username: "test2", keys: "yolo"}

  describe "Resolver: Get an user" do
    test "find_user/3 returns an user by its id", %{conn: conn} do
      user = insert(:user)
      modo = insert(:user, role: :moderator)

      res =
        conn
        |> auth_conn(modo)
        |> AbsintheHelpers.graphql_query(
          query: @get_user_query,
          variables: %{id: user.id}
        )

      assert res["data"]["user"]["email"] == user.email

      res =
        conn
        |> auth_conn(modo)
        |> AbsintheHelpers.graphql_query(
          query: @get_user_query,
          variables: %{id: 0}
        )

      assert res["data"]["user"] == nil
      assert hd(res["errors"])["message"] == "User not found"
    end

    test "get_current_user/3 returns the current logged-in user", %{conn: conn} do
      user = insert(:user)

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @logged_user_query,
          variables: %{}
        )

      assert res["data"]["loggedUser"] == nil

      assert hd(res["errors"])["message"] ==
               "You need to be logged in"

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @logged_user_query,
          variables: %{}
        )

      assert res["data"]["loggedUser"]["id"] == to_string(user.id)
    end
  end

  describe "Resolver: List users" do
    test "list_users/3 doesn't return anything with a non moderator user", %{conn: conn} do
      insert(:user, email: "riri@example.com", role: :moderator)
      user = insert(:user, email: "fifi@example.com")
      insert(:user, email: "loulou@example.com", role: :administrator)

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @list_users_query,
          variables: %{}
        )

      assert hd(res["errors"])["message"] ==
               "You don't have permission to do this"
    end

    test "list_users/3 returns a list of users", %{conn: conn} do
      user = insert(:user, email: "riri@example.com", role: :moderator)
      insert(:user, email: "fifi@example.com")
      insert(:user, email: "loulou@example.com")

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @list_users_query,
          variables: %{}
        )

      assert res["errors"] == nil
      assert res["data"]["users"]["total"] == 3
      assert res["data"]["users"]["elements"] |> length == 3

      assert res["data"]["users"]["elements"]
             |> Enum.map(& &1["email"]) == [
               "loulou@example.com",
               "fifi@example.com",
               "riri@example.com"
             ]

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @list_users_query,
          variables: %{page: 2, limit: 1}
        )

      assert res["errors"] == nil
      assert res["data"]["users"]["total"] == 3
      assert res["data"]["users"]["elements"] |> length == 1

      assert res["data"]["users"]["elements"] |> Enum.map(& &1["email"]) == [
               "fifi@example.com"
             ]

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @list_users_query,
          variables: %{page: 3, limit: 1, sort: "ID", direction: "DESC"}
        )

      assert res["errors"] == nil
      assert res["data"]["users"]["total"] == 3
      assert res["data"]["users"]["elements"] |> length == 1

      assert res["data"]["users"]["elements"] |> Enum.map(& &1["email"]) == [
               "riri@example.com"
             ]
    end

    test "list_users/3 allows filtering the list of users by email", %{conn: conn} do
      user = insert(:user, email: "donald@somewhere.fr", role: :moderator)
      insert(:user, email: "riri@only.fr")
      insert(:user, email: "fifi@we.fr")
      insert(:user, email: "loulou@know.com")

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @list_users_query,
          variables: %{email: "e.fr"}
        )

      assert res["errors"] == nil
      assert res["data"]["users"]["total"] == 2
      assert res["data"]["users"]["elements"] |> length == 2

      assert res["data"]["users"]["elements"]
             |> Enum.map(& &1["email"]) == [
               "fifi@we.fr",
               "donald@somewhere.fr"
             ]
    end

    test "list_users/3 allows filtering the list of users by currentSignInIp", %{conn: conn} do
      user =
        insert(:user,
          email: "donald@somewhere.fr",
          current_sign_in_ip: "144.76.131.212",
          role: :moderator
        )

      insert(:user, email: "riri@only.fr", current_sign_in_ip: "94.130.212.178")
      insert(:user, email: "fifi@we.fr", current_sign_in_ip: "2a01:4f8:221:131d::178")
      insert(:user, email: "loulou@know.com", current_sign_in_ip: "144.76.131.212")

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @list_users_query,
          variables: %{currentSignInIp: "144.76.131.212"}
        )

      assert res["errors"] == nil
      assert res["data"]["users"]["total"] == 2
      assert res["data"]["users"]["elements"] |> length == 2

      assert res["data"]["users"]["elements"]
             |> Enum.map(& &1["email"]) == [
               "loulou@know.com",
               "donald@somewhere.fr"
             ]
    end
  end

  describe "Resolver: Create an user & actor" do
    @user_creation %{
      email: "test@demo.tld",
      password: "long password",
      locale: "fr_FR",
      preferredUsername: "toto",
      name: "Sir Toto",
      summary: "Sir Toto, prince of the functional tests"
    }
    @user_creation_bad_email %{
      email: "y@l@",
      password: "long password"
    }

    test "test create_user/3 creates an user and register_person/3 registers a profile",
         %{conn: conn} do
      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      assert res["data"]["createUser"]["email"] == @user_creation.email
      assert res["data"]["createUser"]["locale"] == @user_creation.locale

      {:ok, user} = Users.get_user_by_email(@user_creation.email)

      assert_email_sent(to: user.email)

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @register_person_mutation,
          variables: @user_creation
        )

      assert res["data"]["registerPerson"]["preferredUsername"] ==
               @user_creation.preferredUsername
    end

    test "create_user/3 doesn't allow two users with the same email", %{conn: conn} do
      res =
        conn
        |> put_req_header("accept-language", "fr")
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      assert res["errors"] == nil
      assert res["data"]["createUser"]["email"] == @user_creation.email

      res =
        conn
        |> put_req_header("accept-language", "fr")
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      assert hd(res["errors"])["message"] == ["Cette adresse e-mail est déjà utilisée."]
    end

    test "create_user/3 doesn't allow registration when registration is closed", %{conn: conn} do
      Config.put([:instance, :registrations_open], false)
      Config.put([:instance, :registration_email_allowlist], [])

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      assert hd(res["errors"])["message"] == "Registrations are not open"
      Config.put([:instance, :registrations_open], true)
    end

    test "create_user/3 doesn't allow registration when user email is not on the allowlist", %{
      conn: conn
    } do
      Config.put([:instance, :registrations_open], false)
      Config.put([:instance, :registration_email_allowlist], ["random.org"])

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      assert hd(res["errors"])["message"] == "Your email is not on the allowlist"
      Config.put([:instance, :registrations_open], true)
      Config.put([:instance, :registration_email_allowlist], [])
    end

    test "create_user/3 allows registration when user email domain is on the allowlist", %{
      conn: conn
    } do
      Config.put([:instance, :registrations_open], false)
      Config.put([:instance, :registration_email_allowlist], ["demo.tld"])

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      refute res["errors"]
      assert res["data"]["createUser"]["email"] == @user_creation.email
      Config.put([:instance, :registrations_open], true)
      Config.put([:instance, :registration_email_allowlist], [])
    end

    test "create_user/3 allows registration when user email is on the allowlist", %{conn: conn} do
      Config.put([:instance, :registrations_open], false)
      Config.put([:instance, :registration_email_allowlist], [@user_creation.email])

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      refute res["errors"]
      assert res["data"]["createUser"]["email"] == @user_creation.email
      Config.put([:instance, :registrations_open], true)
      Config.put([:instance, :registration_email_allowlist], [])
    end

    test "create_user/3 doesn't allow registration when user email domain is on the denylist", %{
      conn: conn
    } do
      Config.put([:instance, :registration_email_denylist], ["demo.tld"])

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      assert hd(res["errors"])["message"] ==
               "Your e-mail has been denied registration or uses a disallowed e-mail provider"

      Config.put([:instance, :registrations_open], true)
      Config.put([:instance, :registration_email_denylist], [])
    end

    test "create_user/3 doesn't allow registration when user email is on the denylist", %{
      conn: conn
    } do
      Config.put([:instance, :registration_email_denylist], [@user_creation.email])

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation
        )

      assert hd(res["errors"])["message"] ==
               "Your e-mail has been denied registration or uses a disallowed e-mail provider"

      Config.put([:instance, :registrations_open], true)
      Config.put([:instance, :registration_email_denylist], [])
    end

    test "create_user/3 doesn't allow registration when user email is on the denylist, even when plus addressing is used",
         %{
           conn: conn
         } do
      Config.put([:instance, :registration_email_denylist], [@user_creation.email])

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: Map.put(@user_creation, :email, "test+alias@demo.tld")
        )

      assert hd(res["errors"])["message"] ==
               "Your e-mail has been denied registration or uses a disallowed e-mail provider"

      Config.put([:instance, :registrations_open], true)
      Config.put([:instance, :registration_email_denylist], [])
    end

    test "create_user/3 lowers domain part of email",
         %{
           conn: conn
         } do
      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: Map.put(@user_creation, :email, "test+alias@DEMO.tld")
        )

      assert res["errors"] == nil
      assert res["data"]["createUser"]["email"] == "test+alias@demo.tld"
    end

    test "register_person/3 doesn't register a profile from an unknown email", %{conn: conn} do
      conn
      |> put_req_header("accept-language", "fr")
      |> AbsintheHelpers.graphql_query(
        query: @create_user_mutation,
        variables: @user_creation
      )

      res =
        conn
        |> put_req_header("accept-language", "fr")
        |> AbsintheHelpers.graphql_query(
          query: @register_person_mutation,
          variables: Map.put(@user_creation, :email, "random")
        )

      assert hd(res["errors"])["message"] ==
               "Aucun·e utilisateur·ice avec cette adresse e-mail n'a été trouvé·e"
    end

    test "register_person/3 can't be called with an existing profile", %{conn: conn} do
      conn
      |> put_req_header("accept-language", "fr")
      |> AbsintheHelpers.graphql_query(
        query: @create_user_mutation,
        variables: @user_creation
      )

      res =
        conn
        |> put_req_header("accept-language", "fr")
        |> AbsintheHelpers.graphql_query(
          query: @register_person_mutation,
          variables: @user_creation
        )

      assert res["data"]["registerPerson"]["preferredUsername"] ==
               @user_creation.preferredUsername

      res =
        conn
        |> put_req_header("accept-language", "fr")
        |> AbsintheHelpers.graphql_query(
          query: @register_person_mutation,
          variables: @user_creation
        )

      assert hd(res["errors"])["message"] ==
               "Vous avez déjà un profil pour cet utilisateur"
    end

    test "register_person/3 is case insensitive", %{conn: conn} do
      insert(:actor, preferred_username: "myactor")

      conn
      |> put_req_header("accept-language", "fr")
      |> AbsintheHelpers.graphql_query(
        query: @create_user_mutation,
        variables: @user_creation
      )

      res =
        conn
        |> put_req_header("accept-language", "fr")
        |> AbsintheHelpers.graphql_query(
          query: @register_person_mutation,
          variables: Map.put(@user_creation, :preferredUsername, "Myactor")
        )

      refute is_nil(res["errors"])

      assert hd(res["errors"])["message"] ==
               ["Cet identifiant est déjà pris."]
    end

    test "test create_user/3 doesn't create an user with bad email", %{conn: conn} do
      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @create_user_mutation,
          variables: @user_creation_bad_email
        )

      assert hd(res["errors"])["message"] ==
               ["Email doesn't fit required format"]
    end
  end

  describe "Resolver: Validate an user" do
    test "test validate_user/3 validates an user", context do
      {:ok, %User{} = user} = Users.register(@valid_actor_params)

      mutation = """
          mutation {
            validateUser(
                  token: "#{user.confirmation_token}"
              ) {
                accessToken,
                user {
                  id,
                },
              }
            }
      """

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert json_response(res, 200)["data"]["validateUser"]["user"]["id"] == to_string(user.id)
    end

    test "test validate_user/3 with invalid token doesn't validate an user", context do
      insert(:user, confirmation_token: "t0t0")

      mutation = """
          mutation {
            validateUser(
                  token: "no pass"
              ) {
                accessToken,
                user {
                  id
                },
              }
            }
      """

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] == "Unable to validate user"
    end
  end

  describe "Resolver: Resend confirmation emails" do
    test "test resend_confirmation_email/3 with valid email resends an validation email",
         context do
      {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})

      mutation = """
          mutation {
            resendConfirmationEmail(
                  email: "#{user.email}"
              )
            }
      """

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] ==
               "You requested again a confirmation email too soon"

      # Hammer time !
      Users.update_user(user, %{
        confirmation_sent_at: Timex.shift(user.confirmation_sent_at, hours: -3)
      })

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert json_response(res, 200)["data"]["resendConfirmationEmail"] == user.email
      assert_email_sent(to: user.email)
    end

    test "test resend_confirmation_email/3 with invalid email resends an validation email",
         context do
      mutation = """
          mutation {
            resendConfirmationEmail(
                  email: "oh@no.com"
              )
            }
      """

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] ==
               "No user to validate with this email was found"
    end
  end

  describe "Resolver: Send reset password" do
    test "test send_reset_password/3 with valid email", %{conn: conn} do
      %User{email: email} = insert(:user)

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @send_reset_password_mutation,
          variables: %{email: email}
        )

      assert res["data"]["sendResetPassword"] == email
    end

    test "test send_reset_password/3 with an email with no account", %{conn: conn} do
      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @send_reset_password_mutation,
          variables: %{email: "noone@nowhere.com"}
        )

      assert hd(res["errors"])["message"] ==
               "No user with this email was found"
    end

    test "test send_reset_password/3 with invalid email", %{conn: conn} do
      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @send_reset_password_mutation,
          variables: %{email: "not an email"}
        )

      assert hd(res["errors"])["message"] ==
               "This email doesn't seem to be valid"
    end

    test "test send_reset_password/3 for an LDAP user", %{conn: conn} do
      {:ok, %User{email: email}} = Users.create_external("some@users.com", "ldap")

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @send_reset_password_mutation,
          variables: %{email: email}
        )

      assert hd(res["errors"])["message"] ==
               "This user can't reset their password"
    end

    test "test send_reset_password/3 for a deactivated user doesn't send email", %{conn: conn} do
      {:ok, %User{email: email} = user} =
        Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})

      Users.update_user(user, %{confirmed_at: DateTime.utc_now(), disabled: true})

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @send_reset_password_mutation,
          variables: %{email: email}
        )

      assert hd(res["errors"])["message"] ==
               "No user with this email was found"
    end
  end

  describe "Resolver: Reset user's password" do
    test "test reset_password/3 with valid email", context do
      {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})
      Users.update_user(user, %{confirmed_at: DateTime.utc_now()})
      %Actor{} = insert(:actor, user: user)
      {:ok, _email_sent} = Email.User.send_password_reset_email(user)
      %User{reset_password_token: reset_password_token} = Users.get_user!(user.id)

      mutation = """
          mutation {
            resetPassword(
                  token: "#{reset_password_token}",
                  password: "new password"
              ) {
                user {
                  id
                }
              }
            }
      """

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert is_nil(json_response(res, 200)["errors"])
      assert json_response(res, 200)["data"]["resetPassword"]["user"]["id"] == to_string(user.id)
    end

    test "test reset_password/3 with a password too short", context do
      %User{} = user = insert(:user)
      {:ok, _email_sent} = Email.User.send_password_reset_email(user)
      %User{reset_password_token: reset_password_token} = Users.get_user!(user.id)

      mutation = """
          mutation {
            resetPassword(
                  token: "#{reset_password_token}",
                  password: "new"
              ) {
                user {
                  id
                }
              }
            }
      """

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] ==
               "The password you have choosen is too short. Please make sure your password contains at least 6 charaters."
    end

    test "test reset_password/3 with an invalid token", context do
      %User{} = user = insert(:user)
      {:ok, _email_sent} = Email.User.send_password_reset_email(user)
      %User{} = Users.get_user!(user.id)

      mutation = """
          mutation {
            resetPassword(
                  token: "not good",
                  password: "new"
              ) {
                user {
                  id
                }
              }
            }
      """

      res =
        context.conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] ==
               "The token you provided is invalid. Make sure that the URL is exactly the one provided inside the email you got."
    end
  end

  describe "Resolver: Login a user" do
    test "test login_user/3 with valid credentials", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})

      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second),
          "confirmation_sent_at" => nil,
          "confirmation_token" => nil
        })

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @login_mutation,
          variables: %{email: user.email, password: user.password}
        )

      assert login = res["data"]["login"]
      assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])
    end

    test "test login_user/3 with invalid password", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})

      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second),
          "confirmation_sent_at" => nil,
          "confirmation_token" => nil
        })

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @login_mutation,
          variables: %{email: user.email, password: "bad password"}
        )

      assert hd(res["errors"])["message"] ==
               "Impossible to authenticate, either your email or password are invalid."
    end

    test "test login_user/3 with invalid email", %{conn: conn} do
      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @login_mutation,
          variables: %{email: "bad email", password: "bad password"}
        )

      assert hd(res["errors"])["message"] ==
               "User not found"
    end

    test "test login_user/3 with unconfirmed user", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @login_mutation,
          variables: %{email: user.email, password: user.password}
        )

      assert hd(res["errors"])["message"] == "User not found"
    end
  end

  describe "Resolver: Refresh a token" do
    @refresh_token_mutation """
    mutation RefreshToken($refreshToken: String!) {
      refreshToken(
        refreshToken: $refreshToken
      ) {
        accessToken
      }
    }
    """

    @logged_person_query """
    query LoggedPerson {
      loggedPerson {
        preferredUsername,
      }
    }
    """

    test "test refresh_token/3 with a bad token", %{conn: conn} do
      res =
        AbsintheHelpers.graphql_query(conn,
          query: @refresh_token_mutation,
          variables: [refreshToken: "bad_token"]
        )

      assert hd(res["errors"])["message"] ==
               "Cannot refresh the token"
    end

    test "test refresh_token/3 with an appropriate token", %{conn: conn} do
      user = insert(:user)
      insert(:actor, user: user)
      {:ok, refresh_token} = Authenticator.generate_refresh_token(user)

      res =
        AbsintheHelpers.graphql_query(conn,
          query: @refresh_token_mutation,
          variables: [refreshToken: refresh_token]
        )

      assert res["errors"] == nil

      access_token = res["data"]["refreshToken"]["accessToken"]
      assert String.length(access_token) > 10

      res =
        conn
        |> Plug.Conn.put_req_header("authorization", "Bearer #{access_token}")
        |> AbsintheHelpers.graphql_query(query: @logged_person_query)

      assert res["errors"] == nil
    end
  end

  describe "Resolver: change default actor for user" do
    test "test change_default_actor/3 without being logged-in", %{conn: conn} do
      # Prepare user with two actors
      user = insert(:user)
      insert(:actor, user: user)

      assert {:ok, %User{actors: _actors}} = Users.get_user_with_actors(user.id)

      actor_params = @valid_single_actor_params |> Map.put(:user_id, user.id)
      assert {:ok, %Actor{} = actor2} = Actors.create_actor(actor_params)

      assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id)
      assert length(actors) == 2

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @change_default_actor,
          variables: %{preferredUsername: actor2.preferred_username}
        )

      assert hd(res["errors"])["message"] == "You need to be logged in"
    end

    test "test change_default_actor/3 with valid actor", %{conn: conn} do
      # Prepare user with two actors
      user = insert(:user)
      insert(:actor, user: user)

      assert {:ok, %User{actors: _actors}} = Users.get_user_with_actors(user.id)

      actor_params = @valid_single_actor_params |> Map.put(:user_id, user.id)
      assert {:ok, %Actor{} = actor2} = Actors.create_actor(actor_params)

      assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id)
      assert length(actors) == 2

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @change_default_actor,
          variables: %{preferredUsername: actor2.preferred_username}
        )

      assert res["errors"] == nil

      assert res["data"]["changeDefaultActor"]["defaultActor"]["preferredUsername"] ==
               actor2.preferred_username
    end
  end

  describe "Resolver: Change password for an user" do
    @email "toto@tata.tld"
    @old_password "p4ssw0rd"
    @new_password "upd4t3d"

    test "change_password/3 with valid password", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @old_password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          "confirmed_at" => Timex.shift(user.confirmation_sent_at, hours: -3),
          "confirmation_sent_at" => nil,
          "confirmation_token" => nil
        })

      mutation = """
          mutation {
            login(
                  email: "#{@email}",
                  password: "#{@old_password}",
              ) {
                accessToken,
                refreshToken,
                user {
                  id
                }
              }
            }
      """

      res =
        conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert login = json_response(res, 200)["data"]["login"]
      assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])

      mutation = """
          mutation {
            changePassword(old_password: "#{@old_password}", new_password: "#{@new_password}") {
                id
              }
            }
      """

      res =
        conn
        |> auth_conn(user)
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert json_response(res, 200)["errors"] == nil
      assert json_response(res, 200)["data"]["changePassword"]["id"] == to_string(user.id)

      mutation = """
          mutation {
            login(
                  email: "#{@email}",
                  password: "#{@new_password}",
              ) {
                accessToken,
                refreshToken,
                user {
                  id
                }
              }
            }
      """

      res =
        conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert login = json_response(res, 200)["data"]["login"]
      assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])
    end

    test "change_password/3 with invalid password", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @old_password})

      # Hammer time !

      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          "confirmed_at" => Timex.shift(user.confirmation_sent_at, hours: -3),
          "confirmation_sent_at" => nil,
          "confirmation_token" => nil
        })

      mutation = """
          mutation {
            changePassword(old_password: "invalid password", new_password: "#{@new_password}") {
                id
              }
            }
      """

      res =
        conn
        |> auth_conn(user)
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] == "The current password is invalid"
    end

    test "change_password/3 with same password", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @old_password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          "confirmed_at" => Timex.shift(user.confirmation_sent_at, hours: -3),
          "confirmation_sent_at" => nil,
          "confirmation_token" => nil
        })

      mutation = """
          mutation {
            changePassword(old_password: "#{@old_password}", new_password: "#{@old_password}") {
                id
              }
            }
      """

      res =
        conn
        |> auth_conn(user)
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] ==
               "The new password must be different"
    end

    test "change_password/3 with new password too short", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @old_password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          "confirmed_at" => Timex.shift(user.confirmation_sent_at, hours: -3),
          "confirmation_sent_at" => nil,
          "confirmation_token" => nil
        })

      mutation = """
          mutation {
            changePassword(old_password: "#{@old_password}", new_password: "new") {
                id
              }
            }
      """

      res =
        conn
        |> auth_conn(user)
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] ==
               "The password you have chosen is too short. Please make sure your password contains at least 6 characters."
    end

    test "change_password/3 without being authenticated", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @old_password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          "confirmed_at" => Timex.shift(user.confirmation_sent_at, hours: -3),
          "confirmation_sent_at" => nil,
          "confirmation_token" => nil
        })

      mutation = """
          mutation {
            changePassword(old_password: "#{@old_password}", new_password: "#{@new_password}") {
                id
              }
            }
      """

      res =
        conn
        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))

      assert hd(json_response(res, 200)["errors"])["message"] ==
               "You need to be logged in"
    end
  end

  describe "Resolver: Change email for an user" do
    @old_email "old@domain.tld"
    @new_email "new@domain.tld"
    @password "p4ssw0rd"

    test "change_email/3 with valid email", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @login_mutation,
          variables: %{email: @old_email, password: @password}
        )

      login = res["data"]["login"]
      assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @change_email_mutation,
          variables: %{email: @new_email, password: @password}
        )

      assert res["errors"] == nil
      assert res["data"]["changeEmail"]["id"] == to_string(user.id)

      user = Users.get_user!(user.id)
      assert user.email == @old_email
      assert user.unconfirmed_email == @new_email

      assert_email_sent(to: user.email)
      assert_email_sent(to: user.unconfirmed_email)

      conn
      |> AbsintheHelpers.graphql_query(
        query: @validate_email_mutation,
        variables: %{token: user.confirmation_token}
      )

      user = Users.get_user!(user.id)
      assert user.email == @new_email
      assert user.unconfirmed_email == nil
    end

    test "change_email/3 with valid email but invalid token", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @login_mutation,
          variables: %{email: @old_email, password: @password}
        )

      login = res["data"]["login"]
      assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @change_email_mutation,
          variables: %{email: @new_email, password: @password}
        )

      assert res["errors"] == nil
      assert res["data"]["changeEmail"]["id"] == to_string(user.id)

      user = Users.get_user!(user.id)
      assert user.email == @old_email
      assert user.unconfirmed_email == @new_email

      assert_email_sent(to: user.email)
      assert_email_sent(to: user.unconfirmed_email)

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @validate_email_mutation,
          variables: %{token: "some token"}
        )

      assert hd(res["errors"])["message"] == "Invalid activation token"

      user = Users.get_user!(user.id)
      assert user.email == @old_email
      assert user.unconfirmed_email == @new_email
    end

    test "change_email/3 with invalid password", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @change_email_mutation,
          variables: %{email: @new_email, password: "invalid_password"}
        )

      assert hd(res["errors"])["message"] == "The password provided is invalid"
    end

    test "change_email/3 with same email", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @change_email_mutation,
          variables: %{email: @old_email, password: @password}
        )

      assert hd(res["errors"])["message"] == "The new email must be different"
    end

    test "change_email/3 with invalid email", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @change_email_mutation,
          variables: %{email: "invalid email", password: @password}
        )

      assert hd(res["errors"])["message"] == "The new email doesn't seem to be valid"
    end

    test "change_password/3 without being authenticated", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @change_email_mutation,
          variables: %{email: @new_email, password: @password}
        )

      assert hd(res["errors"])["message"] ==
               "You need to be logged in"
    end
  end

  describe "Resolver: User deletes it's account" do
    @email "mail@domain.tld"
    @password "p4ssw0rd"

    test "delete_account/3 with valid password", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @password})

      # Hammer time !
      {:ok, %User{} = user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      %Actor{} = actor1 = insert(:actor, user: user)
      %Actor{} = actor2 = insert(:actor, user: user)
      %Event{id: event_id} = event = insert(:event, organizer_actor: actor1)

      %Participant{id: participant_id} =
        insert(:participant, event: event, actor: actor2, role: :participant)

      %Comment{id: comment_id} = insert(:comment, actor: actor2, event: event)

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @delete_user_account_mutation,
          variables: %{password: @password}
        )

      assert res["data"]["deleteAccount"]["id"] == to_string(user.id)

      assert [
               %Oban.Job{args: %{"actor_id" => actor2_id, "op" => "delete_actor"}},
               %Oban.Job{args: %{"actor_id" => actor1_id, "op" => "delete_actor"}}
             ] = all_enqueued(queue: :background)

      assert MapSet.new([actor1.id, actor2.id]) == MapSet.new([actor1_id, actor2_id])

      assert is_nil(Users.get_user(user.id))

      assert %{success: 2, snoozed: 0, failure: 0, discard: 0, cancelled: 0} ==
               Oban.drain_queue(queue: :background)

      assert_raise Ecto.NoResultsError, fn ->
        Events.get_event!(event_id)
      end

      assert_raise Ecto.NoResultsError, fn ->
        Discussions.get_comment!(comment_id)
      end

      # Actors are not deleted but emptied (to keep the  username reserved)
      actor1 = Actors.get_actor!(actor1_id)
      assert actor1.suspended
      assert is_nil(actor1.name)

      actor2 = Actors.get_actor!(actor2_id)
      assert actor2.suspended
      assert is_nil(actor2.name)

      assert is_nil(Events.get_participant(participant_id))
    end

    test "delete_account/3 with 3rd-party auth login", %{conn: conn} do
      {:ok, %User{} = user} = Users.create_external(@email, "keycloak")

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(query: @delete_user_account_mutation)

      assert is_nil(res["errors"])
      assert res["data"]["deleteAccount"]["id"] == to_string(user.id)
    end

    test "delete_account/3 with invalid password", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @password})

      # Hammer time !
      {:ok, %User{} = user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> auth_conn(user)
        |> AbsintheHelpers.graphql_query(
          query: @delete_user_account_mutation,
          variables: %{password: "invalid password"}
        )

      assert hd(res["errors"])["message"] == "The password provided is invalid"
    end

    test "delete_account/3 without being authenticated", %{conn: conn} do
      {:ok, %User{} = user} = Users.register(%{email: @email, password: @password})

      # Hammer time !
      {:ok, %User{} = _user} =
        Users.update_user(user, %{
          confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
          confirmation_sent_at: nil,
          confirmation_token: nil
        })

      res =
        conn
        |> AbsintheHelpers.graphql_query(
          query: @delete_user_account_mutation,
          variables: %{password: "invalid password"}
        )

      assert hd(res["errors"])["message"] ==
               "You need to be logged in"
    end
  end
end
