defmodule LivebookWeb.HomeLiveTest do
  use LivebookWeb.ConnCase, async: true

  import Phoenix.LiveViewTest
  import Livebook.SessionHelpers
  import Livebook.TestHelpers

  alias Livebook.Sessions
  alias Livebook.Session

  test "disconnected and connected render", %{conn: conn} do
    {:ok, view, disconnected_html} = live(conn, ~p"/")
    assert disconnected_html =~ "Running sessions"
    assert render(view) =~ "Running sessions"
  end

  test "redirects to session upon creation", %{conn: conn} do
    {:ok, view, _} = live(conn, ~p"/")

    assert {:error, {:live_redirect, %{to: to}}} =
             view
             |> element(~s/[role="navigation"] a/, "New notebook")
             |> render_click()

    assert to =~ "/sessions/"

    close_session_by_path(to)
  end

  test "public new endpoint creates an empty session", %{conn: conn} do
    assert {:error, {:live_redirect, %{to: to}}} = result = live(conn, ~p"/new")
    {:ok, view, _} = follow_redirect(result, conn)

    assert render(view) =~ "Untitled notebook"

    close_session_by_path(to)
  end

  describe "sessions list" do
    test "lists running sessions", %{conn: conn} do
      {:ok, session1} = Sessions.create_session()
      {:ok, session2} = Sessions.create_session()

      {:ok, view, _} = live(conn, ~p"/")

      assert render(view) =~ session1.id
      assert render(view) =~ session2.id

      Session.close([session1.pid, session2.pid])
    end

    test "updates UI whenever a session is added or deleted", %{conn: conn} do
      Sessions.subscribe()

      {:ok, view, _} = live(conn, ~p"/")

      {:ok, %{id: id} = session} = Sessions.create_session()
      assert_receive {:session_created, %{id: ^id}}
      assert render(view) =~ id

      Session.close(session.pid)
      assert_receive {:session_closed, %{id: ^id}}
      refute render(view) =~ id
    end

    test "allows download the source of an existing session", %{conn: conn} do
      {:ok, session} = Sessions.create_session()
      Session.set_notebook_name(session.pid, "My notebook")

      {:ok, view, _} = live(conn, ~p"/")

      {:error, {:redirect, %{to: to}}} =
        view
        |> element(~s{[data-test-session-id="#{session.id}"] a}, "Download source")
        |> render_click

      assert to == ~p"/sessions/#{session.id}/download/export/livemd?include_outputs=false"

      Session.close(session.pid)
    end

    test "allows forking existing session", %{conn: conn} do
      {:ok, session} = Sessions.create_session()
      Session.set_notebook_name(session.pid, "My notebook")

      {:ok, view, _} = live(conn, ~p"/")

      assert {:error, {:live_redirect, %{to: to}}} =
               view
               |> element(~s{[data-test-session-id="#{session.id}"] button}, "Fork")
               |> render_click()

      assert to =~ "/sessions/"

      {:ok, view, _} = live(conn, to)
      assert render(view) =~ "My notebook - fork"

      close_session_by_path(to)
      Session.close(session.pid)
    end

    test "allows closing session after confirmation", %{conn: conn} do
      {:ok, %{id: id} = session} = Sessions.create_session()

      {:ok, view, _} = live(conn, ~p"/")

      assert render(view) =~ session.id

      view
      |> element(~s{[data-test-session-id="#{session.id}"] button}, "Close")
      |> render_click()

      Sessions.subscribe()

      render_confirm(view)

      assert_receive {:session_closed, %{id: ^id}}

      refute render(view) =~ session.id
    end

    test "close all selected sessions using bulk action", %{conn: conn} do
      {:ok, %{id: id1} = session1} = Sessions.create_session()
      {:ok, %{id: id2} = session2} = Sessions.create_session()
      {:ok, %{id: id3} = session3} = Sessions.create_session()

      {:ok, view, _} = live(conn, ~p"/")

      assert render(view) =~ session1.id
      assert render(view) =~ session2.id
      assert render(view) =~ session3.id

      view
      |> form("#bulk-action-form", %{
        "action" => "close_all",
        "session_ids" => [session1.id, session2.id, session3.id]
      })
      |> render_submit()

      assert render(view) =~ "Are you sure you want to close 3 sessions?"

      Sessions.subscribe()

      render_confirm(view)

      assert_receive {:session_closed, %{id: ^id1}}
      assert_receive {:session_closed, %{id: ^id2}}
      assert_receive {:session_closed, %{id: ^id3}}

      refute render(view) =~ session1.id
      refute render(view) =~ session2.id
      refute render(view) =~ session3.id
    end
  end

  describe "starred notebooks" do
    @tag :tmp_dir
    test "starring and unstarring a notebook", %{conn: conn, tmp_dir: tmp_dir} do
      notebook_path = Path.join(tmp_dir, "notebook.livemd")
      file = Livebook.FileSystem.File.local(notebook_path)

      {:ok, session} = Sessions.create_session()
      Session.set_file(session.pid, file)
      Session.set_notebook_name(session.pid, "Special notebook")

      wait_for_session_update(session.pid)

      {:ok, view, _} = live(conn, ~p"/")

      Livebook.NotebookManager.subscribe_starred_notebooks()

      Livebook.NotebookManager.add_starred_notebook(file, "Special notebook")
      assert_receive {:starred_notebooks_updated, _}

      assert view
             |> element(~s/#starred-notebooks/, "Special notebook")
             |> has_element?()

      Livebook.NotebookManager.remove_starred_notebook(file)
      assert_receive {:starred_notebooks_updated, _}

      refute view
             |> element(~s/#starred-notebooks/, "Special notebook")
             |> has_element?()
    end

    @tag :tmp_dir
    test "opening a starred notebook", %{conn: conn, tmp_dir: tmp_dir} do
      notebook_path = Path.join(tmp_dir, "notebook.livemd")
      File.write!(notebook_path, "# Starred notebook")
      file = Livebook.FileSystem.File.local(notebook_path)
      Livebook.NotebookManager.add_starred_notebook(file, "Starred notebook")

      {:ok, view, _} = live(conn, ~p"/")

      card_html =
        view
        |> element(~s/#starred-notebooks [data-test-idx]/, "Starred notebook")
        |> render()

      assert [attr] = Regex.run(~r/data-test-idx="\d+"/, card_html)

      view
      |> element(~s/#starred-notebooks [#{attr}] button/, "Open")
      |> render_click()

      {to, _flash} = assert_redirect(view)

      assert to =~ "/sessions/"

      {:ok, view, _} = live(conn, to)
      assert render(view) =~ "Starred notebook"

      close_session_by_path(to)
    end
  end

  describe "hubs" do
    test "renders persisted hubs in the sidebar", %{conn: conn} do
      team = Livebook.HubHelpers.offline_hub()

      {:ok, view, _} = live(conn, ~p"/")
      assert render(view) =~ "WORKSPACES"
      assert render(view) =~ team.hub_name
      assert render(view) =~ "Add Organization"
    end
  end

  test "handles user profile update", %{conn: conn} do
    {:ok, view, _} = live(conn, ~p"/")
    data = %{user: %{name: "Jake Peralta", hex_color: "#123456"}}

    view
    |> element("#user_form")
    |> render_change(data)

    view
    |> element("#user_form")
    |> render_submit(data)

    assert render(view) =~ "Jake Peralta"
    assert render(view) =~ "#123456"
  end

  # Helpers

  defp close_session_by_path("/sessions/" <> session_id) do
    {:ok, session} = Sessions.fetch_session(session_id)
    Session.close(session.pid)
  end
end
