defmodule TarakanWeb.AccountSessionControllerTest do
  use TarakanWeb.ConnCase, async: true

  import Tarakan.AccountsFixtures
  alias Tarakan.Accounts

  setup do
    %{unconfirmed_account: unconfirmed_account_fixture(), account: account_fixture()}
  end

  describe "POST /accounts/log-in - handle/email and password" do
    test "logs the account in", %{conn: conn, account: account} do
      account = set_password(account)

      conn =
        post(conn, ~p"/accounts/log-in", %{
          "account" => %{"identifier" => account.handle, "password" => valid_account_password()}
        })

      assert get_session(conn, :account_token)
      assert redirected_to(conn) == ~p"/"

      # Now do a logged in request and assert on the menu
      conn = get(conn, ~p"/")
      response = html_response(conn, 200)
      assert response =~ "@#{account.handle}"
      assert response =~ ~p"/accounts/settings"
      assert response =~ ~p"/accounts/log-out"
    end

    test "logs the account in with remember me", %{conn: conn, account: account} do
      account = set_password(account)

      conn =
        post(conn, ~p"/accounts/log-in", %{
          "account" => %{
            "identifier" => account.email,
            "password" => valid_account_password(),
            "remember_me" => "true"
          }
        })

      assert conn.resp_cookies["_tarakan_web_account_remember_me"]
      assert redirected_to(conn) == ~p"/"
    end

    test "logs the account in with return to", %{conn: conn, account: account} do
      account = set_password(account)

      conn =
        conn
        |> init_test_session(account_return_to: "/foo/bar")
        |> post(~p"/accounts/log-in", %{
          "account" => %{
            "identifier" => account.handle,
            "password" => valid_account_password()
          }
        })

      assert redirected_to(conn) == "/foo/bar"
      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Welcome back!"
    end

    test "redirects to login page with invalid credentials", %{conn: conn, account: account} do
      conn =
        post(conn, ~p"/accounts/log-in?mode=password", %{
          "account" => %{"identifier" => account.handle, "password" => "invalid_password"}
        })

      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
               "Invalid handle, email, or password"

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

  describe "POST /accounts/log-in - magic link" do
    test "logs the account in", %{conn: conn, account: account} do
      {token, _hashed_token} = generate_account_magic_link_token(account)

      conn =
        post(conn, ~p"/accounts/log-in", %{
          "account" => %{"token" => token}
        })

      assert get_session(conn, :account_token)
      assert redirected_to(conn) == ~p"/"

      # Now do a logged in request and assert on the menu
      conn = get(conn, ~p"/")
      response = html_response(conn, 200)
      assert response =~ "@#{account.handle}"
      assert response =~ ~p"/accounts/settings"
      assert response =~ ~p"/accounts/log-out"
    end

    test "confirms unconfirmed account", %{conn: conn, unconfirmed_account: account} do
      {token, _hashed_token} = generate_account_magic_link_token(account)
      refute account.confirmed_at

      conn =
        post(conn, ~p"/accounts/log-in", %{
          "account" => %{"token" => token},
          "_action" => "confirmed"
        })

      assert get_session(conn, :account_token)
      assert redirected_to(conn) == ~p"/"
      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Account confirmed successfully."

      assert Accounts.get_account!(account.id).confirmed_at

      # Now do a logged in request and assert on the menu
      conn = get(conn, ~p"/")
      response = html_response(conn, 200)
      assert response =~ "@#{account.handle}"
      assert response =~ ~p"/accounts/settings"
      assert response =~ ~p"/accounts/log-out"
    end

    test "redirects to login page when magic link is invalid", %{conn: conn} do
      conn =
        post(conn, ~p"/accounts/log-in", %{
          "account" => %{"token" => "invalid"}
        })

      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
               "The link is invalid or it has expired."

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

    test "redirects to login page for a crafted non-base64 magic link", %{conn: conn} do
      conn =
        post(conn, ~p"/accounts/log-in", %{
          "account" => %{"token" => "not base64!"}
        })

      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
               "The link is invalid or it has expired."

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

  describe "POST /accounts/update-password" do
    test "updates the password for a confirmed account in sudo mode", %{
      conn: conn,
      account: account
    } do
      conn =
        conn
        |> log_in_account(account)
        |> post(~p"/accounts/update-password", %{
          "account" => %{
            "password" => valid_account_password(),
            "password_confirmation" => valid_account_password()
          }
        })

      assert redirected_to(conn) == ~p"/accounts/settings"
      assert Accounts.get_account_by_email_and_password(account.email, valid_account_password())
    end

    test "redirects to re-authentication when the sudo window expired", %{
      conn: conn,
      account: account
    } do
      stale_at = DateTime.add(DateTime.utc_now(:second), -3, :hour)

      conn =
        conn
        |> log_in_account(account, token_authenticated_at: stale_at)
        |> post(~p"/accounts/update-password", %{
          "account" => %{
            "password" => valid_account_password(),
            "password_confirmation" => valid_account_password()
          }
        })

      assert redirected_to(conn) == ~p"/accounts/log-in?return_to=%2Faccounts%2Fsettings"
      refute Accounts.get_account_by_email_and_password(account.email, valid_account_password())
    end

    test "refuses to establish a password before the email is confirmed", %{
      conn: conn,
      unconfirmed_account: account
    } do
      conn =
        conn
        |> log_in_account(account)
        |> post(~p"/accounts/update-password", %{
          "account" => %{
            "password" => valid_account_password(),
            "password_confirmation" => valid_account_password()
          }
        })

      assert redirected_to(conn) == ~p"/accounts/settings"
      refute Accounts.get_account!(account.id).hashed_password
    end
  end

  describe "DELETE /accounts/log-out" do
    test "logs the account out", %{conn: conn, account: account} do
      conn = conn |> log_in_account(account) |> delete(~p"/accounts/log-out")
      assert redirected_to(conn) == ~p"/"
      refute get_session(conn, :account_token)
      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
    end

    test "succeeds even if the account is not logged in", %{conn: conn} do
      conn = delete(conn, ~p"/accounts/log-out")
      assert redirected_to(conn) == ~p"/"
      refute get_session(conn, :account_token)
      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
    end
  end
end
