# frozen_string_literal: true

require "test_helper"

class NotesControllerTest < ActionDispatch::IntegrationTest
  include ActiveJob::TestHelper

  setup do
    @user = create(:user)
    @other_user = create(:user)
  end

  test "GET /:user/notes" do
    public_notes = create_list(:note, 3, user: @user, privacy: :public)
    private_notes = create_list(:note, 2, user: @user, privacy: :private)
    get user_notes_path(@user)
    assert_equal 200, response.status
    assert_select ".user-notes" do
      assert_select ".sub-title .title", text: "Notes"
      assert_select ".recent-note-item", 3
      assert_select ".recent-note-item .action a.btn-edit", 0
      assert_select ".recent-note-item .action a.btn-delete", 0
    end

    # with anonymous disable
    Setting.stub(:anonymous_enable?, false) do
      assert_require_user do
        get user_notes_path(@user)
      end
    end

    sign_in @user
    get user_notes_path(@user)
    assert_equal 200, response.status
    assert_select ".user-notes" do
      assert_select ".sub-title .title", text: "Notes"
      assert_select ".recent-note-item", 5
      assert_select ".recent-note-item .action a.btn-edit", 5
      assert_select ".recent-note-item .action a.btn-delete", 5
    end

    public_notes = create_list(:note, 3, user: @other_user, privacy: :public)
    private_notes = create_list(:note, 2, user: @other_user, privacy: :private)
    get user_notes_path(@other_user)
    assert_equal 200, response.status
    assert_select ".user-notes" do
      assert_select ".sub-title .title", text: "Notes"
      assert_select ".recent-note-item", 3
      assert_select ".recent-note-item .action a.btn-edit", 0
      assert_select ".recent-note-item .action a.btn-delete", 0
    end
  end

  test "GET /notes/new" do
    assert_require_user do
      get new_note_path
    end

    sign_in @user
    get new_note_path
    assert_equal 200, response.status
    assert_react_component "notes/NewNote" do |props|
      assert_equal user_notes_path(@user), props[:action]
      assert_nil props[:note][:slug]
      assert_nil props[:note][:title]
      assert_equal @user.as_json(only: %i[id slug name], methods: :to_url), props[:user].deep_stringify_keys
    end
  end

  test "POST /:user/notes" do
    assert_require_user do
      post user_notes_path(@user), params: {note: {}}
    end

    sign_in @user
    note_params = {
      slug: "foo-bar"
    }
    post user_notes_path(@user), params: {note: note_params}
    assert_equal 200, response.status
    assert_react_component "notes/NewNote" do |props|
      assert_equal user_notes_path(@user), props[:action]
      assert_equal "foo-bar", props[:note][:slug]
      assert_nil props[:note][:title]
      assert_equal({title: "Title can't be blank"}, props[:note][:errors])
      assert_equal @user.as_json(only: %i[id slug name], methods: :to_url), props[:user].deep_stringify_keys
    end

    # Create with Markdown
    note_params = {
      slug: "foo-bar",
      title: "Hello world",
      description: "This is description",
      privacy: "private",
      format: "markdown"
    }
    post user_notes_path(@user), params: {note: note_params}
    assert_redirected_to @user.to_path("/notes/foo-bar/edit")

    note = @user.notes.order("id asc").last
    assert_equal note_params[:slug], note.slug
    assert_equal note_params[:title], note.title
    assert_equal note_params[:description], note.description
    assert_equal true, note.private?
    assert_equal "markdown", note.format

    # Create with SML
    note_params = {
      slug: "foo-bar-sml",
      title: "Hello world - SML",
      description: "This is description",
      privacy: "public",
      format: "sml"
    }
    post user_notes_path(@user), params: {note: note_params}
    assert_redirected_to @user.to_path("/notes/foo-bar-sml/edit")

    note = @user.notes.order("id asc").last
    assert_equal note_params[:slug], note.slug
    assert_equal note_params[:title], note.title
    assert_equal note_params[:description], note.description
    assert_equal false, note.private?
    assert_equal "sml", note.format
  end

  test "GET /:user/notes/:slug" do
    note = create(:note, user: @user)
    get note.to_path
    assert_equal 200, response.status
    assert_match(/#{note.title}/, response.body)
    assert_select ".markdown-body"
    assert_select ".label.label-private", 0
    assert_select ".navbar-title a.user-name" do
      assert_select "[href=?]", @user.to_path
    end

    # reactions
    assert_react_component "reactions/Index" do |props|
      assert_equal "Note", props[:subjectType]
      assert_equal note.id, props[:subjectId]
      assert_equal note.reactions_as_json.sort, props[:reactions].sort
    end

    # comments
    assert_react_component "comments/Index" do |props|
      assert_nil props[:currentUser]
      assert_equal "Note", props[:commentableType]
      assert_equal note.id, props[:commentableId]
      assert_equal "unwatch", props[:watchStatus]
    end

    sign_in @user
    get note.to_path
    assert_equal 200, response.status
    assert_react_component "comments/Index" do |props|
      assert_equal @user.as_json(only: %i[id slug name avatar_url]), props[:currentUser].deep_stringify_keys
      assert_equal "Note", props[:commentableType]
      assert_equal note.id, props[:commentableId]
      assert_equal "watch", props[:watchStatus]
      assert_equal false, props[:abilities][:update]
      assert_equal false, props[:abilities][:destroy]
    end
    assert_select ".doc-share-button-box", 0

    # private
    note = create(:note, user: @other_user, privacy: :private)

    sign_out @user
    get note.to_path
    assert_equal 403, response.status

    sign_in @other_user
    get note.to_path
    assert_equal 200, response.status
    assert_select ".label-private"
  end

  test "GET /:user/notes/:slug with check prev / next link" do
    create(:note, user: @user, privacy: :private)
    create_list(:note, 3, user: @user)

    notes = @user.notes.order("id desc")

    get notes[0].to_path
    assert_equal 200, response.status
    assert_select ".between-docs" do
      assert_select "a.btn-prev", 0
      assert_select "a.btn-next", text: notes[1].title do
        assert_select "[href=?]", notes[1].to_path
      end
    end

    get notes[1].to_path
    assert_equal 200, response.status
    assert_select ".between-docs" do
      assert_select "a.btn-prev", text: notes[0].title do
        assert_select "[href=?]", notes[0].to_path
      end
      assert_select "a.btn-next", text: notes[2].title do
        assert_select "[href=?]", notes[2].to_path
      end
    end

    # Without user will not give last private note
    get notes[2].to_path
    assert_equal 200, response.status
    assert_select ".between-docs" do
      assert_select "a.btn-prev", text: notes[1].title do
        assert_select "[href=?]", notes[1].to_path
      end
      assert_select "a.btn-next", 0
    end

    sign_in @user
    get notes[2].to_path
    assert_equal 200, response.status
    assert_select ".between-docs" do
      assert_select "a.btn-prev", text: notes[1].title do
        assert_select "[href=?]", notes[1].to_path
      end
      assert_select "a.btn-next", text: notes[3].title do
        assert_select "[href=?]", notes[3].to_path
      end
    end
  end

  test "GET /:user/notes/:slug/edit" do
    note = create(:note, user: @user, body: "Hello", body_sml: "Hello sml")

    sign_in @other_user
    get note.to_path("/edit")
    assert_equal 403, response.status

    sign_in @user
    get note.to_path("/edit")
    assert_equal 200, response.status
  end

  test "PUT /:user/notes/:slug with publish" do
    other_note = create(:note, user: @user, slug: "other-note")
    note = create(:note, user: @user)

    sign_in @user
    note_path = user_note_path(@user, note)
    old_note_slug = note.slug

    note_params = {
      title: "New title",
      slug: "other-note",
      body: "New body",
      body_sml: "Bla bla",
      format: "sml"
    }
    put note.to_path, params: {note: note_params}
    assert_equal 200, response.status
    assert_select "form[action=?]", note_path
    assert_select "details.note-validation-error" do
      assert_select "li", text: "Note path has already been taken"
    end

    note_params[:slug] = old_note_slug
    put note.to_path, params: {note: note_params}
    assert_redirected_to note.to_path

    note.reload
    assert_equal note_params[:title], note.title
    assert_equal note_params[:body], note.body_plain
    assert_equal note_params[:body_sml], note.body_sml_plain
    assert_equal note_params[:format], note.format

    # shoud save with JSON API
    put note.to_path, params: {note: {slug: "", title: ""}, format: :json}
    assert_equal 200, response.status
    res = JSON.parse(response.body)
    assert_equal false, res["ok"]
    assert_equal true, res["messages"].is_a?(Array)
    assert_equal true, res["messages"].length > 0

    put note.to_path, params: {note: {slug: "Hello world", description: "New description", privacy: "private"}, format: :json}
    assert_equal 200, response.status
    res = JSON.parse(response.body)
    assert_equal true, res["ok"]
    assert_equal "Hello-world", res["note"]["slug"]

    note.reload
    assert_equal "Hello-world", note.slug
    assert_equal "New description", note.description
    assert_equal true, note.private?
  end

  test "DELETE /:user/notes/:slug" do
    note = create(:note, user: @user)
    assert_require_user do
      delete note.to_path
    end

    sign_in @other_user
    delete note.to_path
    assert_equal 403, response.status

    sign_in @user
    delete note.to_path
    assert_redirected_to @user.to_path("/notes")

    note = Note.find_by_id(note.id)
    assert_nil note
  end

  test "GET /:user/notes/:slug/raw" do
    note = create(:note, user: @user, body: "Hello world")
    get note.to_path("/raw.txt")
    assert_equal 200, response.status
    assert_equal note.body_plain, response.body
    assert_equal "text/plain; charset=utf-8", response.headers["Content-Type"]

    get note.to_path("/raw")
    assert_equal 200, response.status
    assert_select ".markdown-body.markdown-raw" do
      assert_react_component "MarkdownRaw" do |props|
        assert_equal note.body_plain, props[:value]
      end
    end

    # private
    note = create(:note, user: @other_user, privacy: :private)

    get note.to_path("/raw")
    assert_equal 403, response.status

    sign_in @user
    get note.to_path("/raw")
    assert_equal 403, response.status

    sign_in @other_user
    get note.to_path("/raw")
    assert_equal 200, response.status
  end

  test "GET /:user/notes/:slug/versions" do
    note = create(:note, user: @user, body: "Hello world")
    versions = create_list(:version, 20, type: "NoteVersion", subject: note)
    assert_require_user do
      get note.to_path("/versions")
    end

    sign_in @other_user
    get note.to_path("/versions")
    assert_equal 403, response.status

    sign_in @user
    get note.to_path("/versions")
    assert_equal 200, response.status

    previous_version = create(:version, type: "NoteVersion", subject: note)
    last_version = create(:version, type: "NoteVersion", subject: note)
    get note.to_path("/versions")
    assert_equal 200, response.status
    assert_select ".version-item", 8
    assert_select ".version-item .current", 1
    assert_select ".version-item.selected", 1
    assert_select ".version-items .version-item", 7
    assert_select ".version-items .version-item .current", 0
    assert_select ".version-preview .markdown-body", html: last_version.body_html
    assert_select "#previus-version-content", html: previous_version.body_html

    # paginate with remote: true
    get note.to_path("/versions"), xhr: true, params: {page: 2}
    assert_equal 200, response.status
    assert_match %($(".version-item-" + selectedVersionId).addClass("selected");), response.body
  end

  test "PATCH /:user/notes/:slug/revert" do
    note = create(:note, user: @user, body: "Hello world")
    version = Version.first
    note.update(body: "World hello")

    assert_equal "World hello", note.body_plain

    sign_in @other_user
    patch note.to_path("/revert"), params: {version_id: version.id}
    assert_equal 403, response.status

    sign_in @user
    patch note.to_path("/revert"), params: {version_id: version.id}
    assert_redirected_to note.to_path
    note = Note.find_by_id(note.id)
    assert_equal "Hello world", note.body_plain
  end

  test "POST/DELETE /:user/notes/:slug/action" do
    private_note = create(:note, user: @other_user, privacy: :private)
    note = create(:note, user: @user, body: "Hello world")

    post note.to_path("/action"), params: {action_type: "star"}, xhr: true
    assert_equal 401, response.status

    sign_in @user
    post note.to_path("/action"), params: {action_type: "star"}, xhr: true
    assert_equal 200, response.status
    assert_match(/.note-#{note.id}-star-button/, response.body)
    assert_match(/btn.attr\("data-undo-label"\)/, response.body)
    assert_equal true, @user.star_note?(note)

    post private_note.to_path("/action"), params: {action_type: "star"}, xhr: true
    assert_equal 403, response.status

    delete note.to_path("/action"), params: {action_type: "star"}, xhr: true
    assert_equal 200, response.status
    assert_match(/.note-#{note.id}-star-button/, response.body)
    assert_match(/btn.attr\("data-label"\)/, response.body)
    assert_equal false, @user.star_note?(note)
  end

  test "GET /:user/notes/:slug with readers" do
    note = create(:note)

    user = create(:user)
    users = create_list(:user, 8)

    users.map { |u| u.read_note(note) }

    sign_in user

    get note.to_path
    assert_equal 200, response.status
    assert_select ".note-readers" do
      assert_select "a.readers-link .avatar", 5
    end
    assert_equal true, user.read_note?(note)
  end

  test "GET /:user/notes/:slug/readers" do
    note = create(:note)
    users = create_list(:user, 8)
    users.map { |u| u.read_note(note) }

    get note.to_path("/readers"), xhr: true
    assert_equal 200, response.status

    assert_match %(document.querySelector(".note-readers").outerHTML = ), response.body
  end

  test "GET /:user/notes/:slug with PDF export" do
    note = create(:note)
    get note.to_path
    assert_equal 200, response.status

    assert_select ".doc-export-pdf-box", 0

    sign_in @user
    note = create(:note, user: @user)
    get note.to_path
    assert_select ".doc-export-pdf-box details" do
      assert_select ".description", text: "Click button to genrate PDF file for this document."
      assert_select ".btn-generate-pdf" do
        assert_select "[href=?]", note.to_path("/pdf?force=1")
        assert_select "[data-method=?]", "post"
        assert_select "[data-remote=?]", "true"
      end
    end

    # pdf in running
    note.set_export_status(:pdf, "running")
    get note.to_path
    assert_equal 200, response.status
    assert_select ".doc-export-pdf-box details" do
      assert_select ".pdf-export-running"
      assert_select ".btn-generate-pdf", 0
      assert_select ".pdf-export-retry-message" do
        assert_select "a", text: "retry" do
          assert_select "[href=?]", note.to_path("/pdf?force=1")
          assert_select "[data-method=?]", "post"
          assert_select "[data-remote=?]", "true"
        end
      end
    end

    # pdf has done
    note.set_export_status(:pdf, "done")
    note.pdf.attach(io: load_file("blank.png"), filename: "foobar.pdf")
    get note.to_path
    assert_equal 200, response.status
    assert_select ".doc-export-pdf-box details" do
      assert_select ".description", text: "PDF of this document page has been generated."
      assert_select ".btn-download-pdf" do
        assert_select "[href=?]", note.export_url(:pdf)
      end
      assert_select ".btn-regenerate-pdf" do
        assert_select "[href=?]", note.to_path("/pdf?force=1")
        assert_select "[data-method=?]", "post"
        assert_select "[data-remote=?]", "true"
      end
    end
  end

  test "POST /:user/notes/:slug/pdf" do
    def assert_has_pdf_js(response)
      assert_match %(var openStatus = $(".doc-export-pdf-box details[open]");), response.body
      assert_match %($(".doc-export-pdf-box").replaceWith(html);), response.body
      assert_match %($(".doc-export-pdf-box details").attr("open", "");), response.body
    end

    note = create(:note)

    post note.to_path("/pdf"), xhr: true
    assert_equal 401, response.status

    sign_in @user
    post note.to_path("/pdf"), xhr: true
    assert_equal 403, response.status

    note = create(:note, user: @user)
    assert_no_enqueued_jobs only: PDFExportJob do
      post note.to_path("/pdf"), xhr: true
    end
    assert_equal 200, response.status
    assert_has_pdf_js response
    assert_match %(btn-generate-pdf), response.body

    # generate
    assert_enqueued_with job: PDFExportJob do
      post note.to_path("/pdf?force=1"), xhr: true
    end
    assert_equal 200, response.status
    assert_equal "running", note.export_pdf_status.value
    assert_has_pdf_js response
    assert_match %(pdf-export-running), response.body
    assert_match %(pdf-export-retry-message), response.body

    # check status
    post note.to_path("/pdf?check=1"), xhr: true
    assert_equal 200, response.status
    assert_equal "", response.body.strip

    note.set_export_status(:pdf, "done")
    post note.to_path("/pdf?check=1"), xhr: true
    assert_equal 200, response.status
    assert_has_pdf_js response
    assert_match %(btn-generate-pdf), response.body

    note.pdf.attach(io: load_file("blank.png"), filename: "blank.pdf")
    post note.to_path("/pdf?check=1"), xhr: true
    assert_equal 200, response.status
    assert_has_pdf_js response
    assert_match %(btn-regenerate-pdf), response.body
    assert_match %(btn-download-pdf), response.body

    post note.to_path("/pdf"), xhr: true
    assert_equal 200, response.status
    assert_has_pdf_js response
    assert_match %(btn-regenerate-pdf), response.body
    assert_match %(btn-download-pdf), response.body
  end
end
