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

  import Phoenix.LiveViewTest

  alias Livebook.Sessions
  alias Livebook.Session
  alias Livebook.FileSystem

  describe "file selection" do
    test "does not mention autosaving if disabled", %{conn: conn} do
      refute conn |> get(~p"/open/storage") |> html_response(200) =~
               "Looking for unsaved notebooks?"
    end

    test "updates the list of files as the input changes", %{conn: conn} do
      {:ok, view, _} = live(conn, ~p"/open/storage")

      path = Path.expand("../../../lib", __DIR__) <> "/"

      view
      |> element(~s{form[id*="path-form"]})
      |> render_change(%{path: path})

      # Render the view separately to make sure it received the :set_file event
      assert render(view) =~ "livebook_web"
    end

    test "allows importing when a notebook file is selected", %{conn: conn} do
      {:ok, view, _} = live(conn, ~p"/open/storage")

      path = test_notebook_path("basic")

      view
      |> element(~s{form[id*="path-form"]})
      |> render_change(%{path: Path.dirname(path) <> "/"})

      view
      |> element("button", "basic.livemd")
      |> render_click()

      view
      |> element(~s{button[phx-click="fork"]}, "Fork")
      |> render_click()

      {to, _flash} = assert_redirect(view)

      assert to =~ "/sessions/"

      close_session_by_path(to)
    end

    @tag :tmp_dir
    test "disables import when a directory is selected", %{conn: conn, tmp_dir: tmp_dir} do
      {:ok, view, _} = live(conn, ~p"/open/storage")

      view
      |> element(~s{form[id*="path-form"]})
      |> render_change(%{path: tmp_dir <> "/"})

      assert view
             |> element(~s{button[phx-click="fork"][disabled]}, "Fork")
             |> has_element?()
    end

    test "disables import when a nonexistent file is selected", %{conn: conn} do
      {:ok, view, _} = live(conn, ~p"/open/storage")

      path = File.cwd!() |> Path.join("nonexistent.livemd")

      view
      |> element(~s{form[id*="path-form"]})
      |> render_change(%{path: path})

      assert view
             |> element(~s{button[phx-click="fork"][disabled]}, "Fork")
             |> has_element?()
    end

    @tag :tmp_dir
    test "disables open when a write-protected notebook is selected",
         %{conn: conn, tmp_dir: tmp_dir} do
      {:ok, view, _} = live(conn, ~p"/open/storage")

      path = Path.join(tmp_dir, "write_protected.livemd")
      File.touch!(path)
      File.chmod!(path, 0o444)

      view
      |> element(~s{form[id*="path-form"]})
      |> render_change(%{path: tmp_dir <> "/"})

      view
      |> element("button", "write_protected.livemd")
      |> render_click()

      assert view
             |> element(~s{button[phx-click="open"][disabled]}, "Open")
             |> has_element?()

      assert view
             |> element(
               ~s{[data-tooltip="This file is write-protected, fork to create an editable copy"]}
             )
             |> has_element?()
    end
  end

  describe "notebook import" do
    test "allows importing notebook directly from source", %{conn: conn} do
      {:ok, view, _} = live(conn, ~p"/open/source")

      notebook_source = """
      # My notebook
      """

      view
      |> element("form", "Import")
      |> render_submit(%{data: %{source: notebook_source}})

      {path, _flash} = assert_redirect(view, 5000)

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

      close_session_by_path(path)
    end

    test "should show info flash with information about the imported notebook", %{conn: conn} do
      {:ok, view, _} = live(conn, ~p"/open/source")

      notebook_source = """
      # My notebook
      """

      view
      |> element("form", "Import")
      |> render_submit(%{data: %{source: notebook_source}})

      {path, flash} = assert_redirect(view, 5000)

      assert flash["info"] =~
               "You have imported a notebook, no code has been executed so far. You should read and evaluate code as needed."

      close_session_by_path(path)
    end

    test "should show warning flash when the imported notebook have errors", %{conn: conn} do
      {:ok, view, _} = live(conn, ~p"/open/source")

      # Notebook with 3 headers
      notebook_source = """
      # My notebook
      # My notebook
      # My notebook
      """

      view
      |> element("form", "Import")
      |> render_submit(%{data: %{source: notebook_source}})

      {path, flash} = assert_redirect(view, 5000)

      assert flash["warning"] =~
               "We found problems while importing the file:\n- downgrading all headings, because 3 instances of heading 1 were found"

      close_session_by_path(path)
    end

    test "allows importing notebook from url and downloads its files", %{conn: conn} do
      bypass = Bypass.open()

      Bypass.expect_once(bypass, "GET", "/notebook.livemd", fn conn ->
        conn
        |> Plug.Conn.put_resp_content_type("text/plain")
        |> Plug.Conn.resp(200, """
        <!-- livebook:{"file_entries":[{"name":"image.jpg","type":"attachment"}]} -->

        # My notebook
        """)
      end)

      Bypass.expect_once(bypass, "GET", "/files/image.jpg", fn conn ->
        conn
        |> Plug.Conn.put_resp_content_type("text/plain")
        |> Plug.Conn.resp(200, "content")
      end)

      notebook_url = "http://localhost:#{bypass.port}/notebook.livemd"

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

      view
      |> element("form", "Import")
      |> render_submit(%{data: %{url: notebook_url}})

      {path, _flash} = assert_redirect(view, 5000)

      "/sessions/" <> session_id = path
      {:ok, session} = Sessions.fetch_session(session_id)

      assert FileSystem.File.resolve(session.files_dir, "image.jpg") |> FileSystem.File.read() ==
               {:ok, "content"}

      close_session_by_path(path)
    end

    test "allows importing notebook from upload", %{conn: conn} do
      {:ok, view, _} = live(conn, ~p"/open/upload")

      view
      |> file_input("#upload-file-form", :file, [
        %{
          last_modified: 1_594_171_879_000,
          name: "notebook.livemd",
          content: """
          # My notebook
          """,
          size: 14,
          type: "text/plain"
        }
      ])
      |> render_upload("notebook.livemd")

      view
      |> element("#upload-file-form")
      |> render_submit()

      {path, _flash} = assert_redirect(view, 5000)

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

      close_session_by_path(path)
    end
  end

  describe "public import endpoint" do
    test "imports notebook from the given url and downloads its files", %{conn: conn} do
      bypass = Bypass.open()

      Bypass.expect_once(bypass, "GET", "/notebook", fn conn ->
        conn
        |> Plug.Conn.put_resp_content_type("text/plain")
        |> Plug.Conn.resp(200, """
        <!-- livebook:{"file_entries":[{"name":"image.jpg","type":"attachment"}]} -->

        # My notebook
        """)
      end)

      Bypass.expect_once(bypass, "GET", "/files/image.jpg", fn conn ->
        conn
        |> Plug.Conn.put_resp_content_type("text/plain")
        |> Plug.Conn.resp(200, "content")
      end)

      notebook_url = "http://localhost:#{bypass.port}/notebook"

      assert {:error, {:live_redirect, %{to: to}}} = live(conn, ~p"/import?url=#{notebook_url}")

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

      "/sessions/" <> session_id = to
      {:ok, session} = Sessions.fetch_session(session_id)

      assert FileSystem.File.resolve(session.files_dir, "image.jpg") |> FileSystem.File.read() ==
               {:ok, "content"}

      close_session_by_path(to)
    end

    @tag :tmp_dir
    test "imports notebook from local file URL", %{conn: conn, tmp_dir: tmp_dir} do
      notebook_path = Path.join(tmp_dir, "notebook.livemd")
      File.write!(notebook_path, "# My notebook")
      notebook_url = "file://" <> notebook_path

      assert {:error, {:live_redirect, %{to: to}}} = live(conn, ~p"/import?url=#{notebook_url}")

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

      close_session_by_path(to)
    end

    test "redirects to the import form on error", %{conn: conn} do
      bypass = Bypass.open()

      Bypass.expect(bypass, "GET", "/notebook", fn conn ->
        Plug.Conn.resp(conn, 404, "Not found")
      end)

      notebook_url = "http://localhost:#{bypass.port}/notebook"

      assert {:error, {:live_redirect, %{to: to}}} = live(conn, ~p"/import?url=#{notebook_url}")

      assert to == ~p"/open/url?url=#{notebook_url}"

      {:ok, view, _} = live(conn, to)
      assert render(view) =~ notebook_url
    end
  end

  describe "public open endpoint" do
    test "checkouts the directory when a directory is passed", %{conn: conn} do
      directory_path = Path.join(File.cwd!(), "test")

      assert {:error, {:live_redirect, %{to: to}}} = live(conn, ~p"/open?path=#{directory_path}")

      assert to == ~p"/open/storage?path=#{directory_path}"

      {:ok, view, _} = live(conn, to)
      assert render(view) =~ directory_path
    end

    @tag :tmp_dir
    test "opens a file when livebook file is passed", %{conn: conn, tmp_dir: tmp_dir} do
      notebook_path = Path.join(tmp_dir, "notebook.livemd")

      :ok = File.write(notebook_path, "# My notebook")

      assert {:error, {:live_redirect, %{flash: %{}, to: to}}} =
               live(conn, ~p"/open?path=#{notebook_path}")

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

      close_session_by_path(to)
    end
  end

  defp test_notebook_path(name) do
    path =
      ["../../support/notebooks", name <> ".livemd"]
      |> Path.join()
      |> Path.expand(__DIR__)

    unless File.exists?(path) do
      raise "Cannot find test notebook with the name: #{name}"
    end

    path
  end

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