# frozen_string_literal: true
require "spec_helper"
require "controllers/api/api_helper"

include ApiHelper

describe "API v2 WorksController - Create works", type: :request do
  let(:archivist) { create(:archivist) }

  describe "API import with a valid archivist" do
    before :each do
      ApiHelper.mock_external
    end

    after :each do
      WebMock.reset!
    end

    it "returns 200 OK when all stories are created" do
      valid_params = {
        archivist: archivist.login,
        works: [
          { external_author_name: "bar",
            external_author_email: "bar@foo.com",
            chapter_urls: ["http://foo"] }
        ]
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

      assert_equal 200, response.status
    end

    it "returns 400 error with an error message when no stories are created" do
      valid_params = {
        archivist: archivist.login,
        works: [
          { external_author_name: "bar",
            external_author_email: "bar@foo.com",
            chapter_urls: ["http://bar"] }
        ]
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

      assert_equal 400, response.status
    end

    it "returns 400 OK with an error message when only some stories are created" do
      valid_params = {
        archivist: archivist.login,
        works: [
          { external_author_name: "bar",
            external_author_email: "bar@foo.com",
            chapter_urls: ["http://foo"] },
          { external_author_name: "bar2",
            external_author_email: "bar2@foo.com",
            chapter_urls: ["http://foo"] }
        ]
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

      assert_equal 400, response.status
    end

    it "returns the original id" do
      valid_params = {
        archivist: archivist.login,
        works: [
          { id: "123",
            external_author_name: "bar",
            external_author_email: "bar@foo.com",
            chapter_urls: ["http://foo"] }
        ]
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
      parsed_body = JSON.parse(response.body, symbolize_names: true)

      expect(parsed_body[:works].first[:original_id]).to eq("123")
    end

    it "returns human-readable messages as an array" do
      valid_params = {
        archivist: archivist.login,
        works: [
          { id: "123",
            external_author_name: "bar",
            external_author_email: "bar@foo.com",
            chapter_urls: ["http://foo"] }
        ]
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
      parsed_body = JSON.parse(response.body, symbolize_names: true)

      expect(parsed_body[:works].first[:messages]).to be_a(Array)
    end

    it "sends claim emails if send_claim_email is true" do
      valid_params = {
        archivist: archivist.login,
        send_claim_emails: 1,
        works: [
          { id: "123",
            external_author_name: "bar",
            external_author_email: "send_invite@ao3.org",
            chapter_urls: ["http://foo"] }
        ]
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
      parsed_body = JSON.parse(response.body, symbolize_names: true)

      expect(parsed_body[:messages]).to include("Claim emails sent to bar.")
    end

    it "returns 400 Bad Request if no works are specified" do
      valid_params = {
        archivist: archivist.login
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

      assert_equal 400, response.status
    end

    it "returns a helpful message if the external work contains no text" do
      valid_params = {
        archivist: archivist.login,
        works: [
          { external_author_name: "bar",
            external_author_email: "bar@foo.com",
            chapter_urls: ["http://no-content"] }
        ]
      }

      post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
      parsed_body = JSON.parse(response.body, symbolize_names: true)

      expect(parsed_body[:works].first[:messages].first).to start_with("Unable to import this work.")
    end

    describe "Provided API metadata should be used if present" do
      before do
        Language.create(short: "es", name: "Español")

        mock_external

        archivist = create(:archivist)

        valid_params = {
          archivist: archivist.login,
          works: [
            { id: "123",
              title: api_fields[:title],
              summary: api_fields[:summary],
              fandoms: api_fields[:fandoms],
              warnings: api_fields[:warnings],
              characters: api_fields[:characters],
              rating: api_fields[:rating],
              relationships: api_fields[:relationships],
              categories: api_fields[:categories],
              additional_tags: api_fields[:freeform],
              language_code: api_fields[:language_code],
              external_author_name: api_fields[:external_author_name],
              external_author_email: api_fields[:external_author_email],
              notes: api_fields[:notes],
              chapter_urls: ["http://foo"]
            }
          ]
        }

        post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
        parsed_body = JSON.parse(response.body, symbolize_names: true)

        @work = Work.find_by_url(parsed_body[:works].first[:original_url])
      end

      after do
        WebMock.reset!
      end

      it "API should override content for Title" do
        expect(@work.title).to eq(api_fields[:title])
      end

      it "API should override content for Summary" do
        expect(@work.summary).to eq("<p>" + api_fields[:summary] + "</p>")
      end

      it "API should override content for Fandoms" do
        expect(@work.fandoms.first.name).to eq(api_fields[:fandoms])
      end
      it "API should override content for Warnings" do
        expect(@work.archive_warnings.first.name).to eq(api_fields[:warnings])
      end
      it "API should override content for Characters" do
        expect(@work.characters.flat_map(&:name)).to eq(api_fields[:characters].split(", "))
      end
      it "API should override content for Ratings" do
        expect(@work.ratings.first.name).to eq(api_fields[:rating])
      end
      it "API should override content for Relationships" do
        expect(@work.relationships.first.name).to eq(api_fields[:relationships])
      end
      it "API should override content for Categories" do
        expect(@work.categories.first.name).to eq(api_fields[:categories])
      end
      it "API should override content for Additional Tags" do
        expect(@work.freeforms.flat_map(&:name)).to eq(api_fields[:freeform].split(", "))
      end
      it "API should override content for Language" do
        expect(Language.find_by(id: @work.language_id).short).to eq(api_fields[:language_code])
      end
      it "API should override content for Notes" do
        expect(@work.notes).to eq("<p>" + api_fields[:notes] + "</p>")
      end
      it "API should override content for Author pseud" do
        expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
      end
    end

    describe "Metadata should be extracted from content if no API metadata is supplied" do
      before do
        mock_external

        archivist = create(:archivist)

        valid_params = {
          archivist: archivist.login,
          works: [
            { external_author_name: api_fields[:external_author_name],
              external_author_email: api_fields[:external_author_email],
              chapter_urls: ["http://foo"] }
          ]
        }

        post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

        parsed_body = JSON.parse(response.body, symbolize_names: true)
        @work = Work.find_by_url(parsed_body[:works].first[:original_url])
        created_user = ExternalAuthor.find_by(email: api_fields[:external_author_email])
        created_user&.destroy
      end

      after do
        WebMock.reset!
      end

      it "Title should be detected from the content" do
        expect(@work.title).to eq(content_fields[:title])
      end
      it "Summary should be detected from the content" do
        expect(@work.summary).to eq("<p>" + content_fields[:summary] + "</p>")
      end
      it "Date should be detected from the content" do
        expect(@work.revised_at.to_date).to eq(content_fields[:date].to_date)
      end
      it "Chapter title should be detected from the content" do
        expect(@work.chapters.first.title).to eq(content_fields[:chapter_title])
      end
      it "Fandoms should be detected from the content" do
        expect(@work.fandoms.first.name).to eq(content_fields[:fandoms])
      end
      it "Warnings should be detected from the content" do
        expect(@work.archive_warnings.first.name).to eq(content_fields[:warnings])
      end
      it "Characters should be detected from the content" do
        expect(@work.characters.flat_map(&:name)).to eq(content_fields[:characters].split(", "))
      end
      it "Ratings should be detected from the content" do
        expect(@work.ratings.first.name).to eq(content_fields[:rating])
      end
      it "Relationships should be detected from the content" do
        expect(@work.relationships.first.name).to eq(content_fields[:relationships])
      end
      it "Categories should be detected from the content" do
        expect(@work.categories).to be_empty
      end
      it "Additional Tags should be detected from the content" do
        expect(@work.freeforms.flat_map(&:name)).to eq(content_fields[:freeform].split(", "))
      end
      it "Notes should be detected from the content" do
        expect(@work.notes).to eq("<p>" + content_fields[:notes] + "</p>")
      end
    end

    describe "Imports should use fallback values or nil if no metadata is supplied" do
      before do
        mock_external

        archivist = create(:archivist)

        valid_params = {
          archivist: archivist.login,
          works: [
            { external_author_name: api_fields[:external_author_name],
              external_author_email: api_fields[:external_author_email],
              chapter_urls: ["http://no-metadata"] }
          ]
        }

        post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

        parsed_body = JSON.parse(response.body, symbolize_names: true)
        @work = Work.find_by_url(parsed_body[:works].first[:original_url])
      end

      after do
        WebMock.reset!
      end

      it "Title should be the default fallback title for imported works" do
        expect(@work.title).to eq("Untitled Imported Work")
      end
      it "Summary should be blank" do
        expect(@work.summary).to eq("")
      end
      it "Date should be todayish" do
        expect(@work.created_at.utc.to_date).to eq(Time.now.getutc.to_date)
      end
      it "Chapter title should be blank" do
        expect(@work.chapters.first.title).to be_nil
      end
      it "Fandoms should be the default Archive fandom ('No Fandom')" do
        expect(@work.fandoms.first.name).to eq(ArchiveConfig.FANDOM_NO_TAG_NAME)
      end
      it "Warnings should be the default Archive warning" do
        expect(@work.archive_warnings.first.name).to eq(ArchiveConfig.WARNING_DEFAULT_TAG_NAME)
      end
      it "Characters should be empty" do
        expect(@work.characters).to be_empty
      end
      it "Ratings should be the default Archive rating" do
        expect(@work.ratings.first.name).to eq(ArchiveConfig.RATING_DEFAULT_TAG_NAME)
      end
      it "Relationships should be empty" do
        expect(@work.relationships).to be_empty
      end
      it "Categories should be empty" do
        expect(@work.categories).to be_empty
      end
      it "Additional Tags should be empty" do
        expect(@work.freeforms).to be_empty
      end
      it "Language should be English" do
        expect(@work.language_id).to eq(Language.find_by(short: "en").id)
      end
      it "Notes should be empty" do
        expect(@work.notes).to be_empty
      end
      it "Author pseud" do
        expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
      end
    end

    describe "Provided API metadata should be used if present and tag detection is turned off" do
      before do
        mock_external

        archivist = create(:archivist)

        valid_params = {
          archivist: archivist.login,
          works: [
            { id: "123",
              title: api_fields[:title],
              detect_tags: false,
              summary: api_fields[:summary],
              fandoms: api_fields[:fandoms],
              warnings: api_fields[:warnings],
              characters: api_fields[:characters],
              rating: api_fields[:rating],
              relationships: api_fields[:relationships],
              categories: api_fields[:categories],
              additional_tags: api_fields[:freeform],
              external_author_name: api_fields[:external_author_name],
              external_author_email: api_fields[:external_author_email],
              notes: api_fields[:notes],
              chapter_urls: ["http://foo"] }
          ]
        }

        post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

        parsed_body = JSON.parse(response.body, symbolize_names: true)
        @work = Work.find_by_url(parsed_body[:works].first[:original_url])
      end

      after do
        WebMock.reset!
      end

      it "API should override content for Title" do
        expect(@work.title).to eq(api_fields[:title])
      end
      it "API should override content for Summary" do
        expect(@work.summary).to eq("<p>" + api_fields[:summary] + "</p>")
      end
      it "Date should be detected from the content" do
        expect(@work.revised_at.to_date).to eq(content_fields[:date].to_date)
      end
      it "Chapter title should be detected from the content" do
        expect(@work.chapters.first.title).to eq(content_fields[:chapter_title])
      end
      it "API should override content for Fandoms" do
        expect(@work.fandoms.first.name).to eq(api_fields[:fandoms])
      end
      it "API should override content for Warnings" do
        expect(@work.archive_warnings.first.name).to eq(api_fields[:warnings])
      end
      it "API should override content for Characters" do
        expect(@work.characters.flat_map(&:name)).to eq(api_fields[:characters].split(", "))
      end
      it "API should override content for Ratings" do
        expect(@work.ratings.first.name).to eq(api_fields[:rating])
      end
      it "API should override content for Relationships" do
        expect(@work.relationships.first.name).to eq(api_fields[:relationships])
      end
      it "API should override content for Categories" do
        expect(@work.categories.first.name).to eq(api_fields[:categories])
      end
      it "API should override content for Additional Tags" do
        expect(@work.freeforms.flat_map(&:name)).to eq(api_fields[:freeform].split(", "))
      end
      it "API should override content for Notes" do
        expect(@work.notes).to eq("<p>" + api_fields[:notes] + "</p>")
      end
      it "API should override content for Author pseud" do
        expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
      end
    end

    describe "Some fields should be detected and others use fallback values or nil if no metadata is supplied and tag detection is turned off" do
      before do
        mock_external

        archivist = create(:archivist)

        valid_params = {
          archivist: archivist.login,
          works: [
            { external_author_name: api_fields[:external_author_name],
              external_author_email: api_fields[:external_author_email],
              detect_tags: false,
              chapter_urls: ["http://foo"] }
          ]
        }

        post "/api/v2/works", params: valid_params.to_json, headers: valid_headers

        parsed_body = JSON.parse(response.body, symbolize_names: true)
        @work = Work.find_by_url(parsed_body[:works].first[:original_url])
      end

      after do
        WebMock.reset!
      end

      it "Title should be detected from the content" do
        expect(@work.title).to eq(content_fields[:title])
      end
      it "Summary should be detected from the content" do
        expect(@work.summary).to eq("<p>" + content_fields[:summary] + "</p>")
      end
      it "Date should be detected from the content" do
        expect(@work.revised_at.to_date).to eq(content_fields[:date].to_date)
      end
      it "Chapter title should be detected from the content" do
        expect(@work.chapters.first.title).to eq(content_fields[:chapter_title])
      end
      it "Fandoms should be the default Archive fandom ('No Fandom')" do
        expect(@work.fandoms.first.name).to eq(ArchiveConfig.FANDOM_NO_TAG_NAME)
      end
      it "Warnings should be the default Archive warning" do
        expect(@work.archive_warnings.first.name).to eq(ArchiveConfig.WARNING_DEFAULT_TAG_NAME)
      end
      it "Characters should be empty" do
        expect(@work.characters).to be_empty
      end
      it "Ratings should be the default Archive rating" do
        expect(@work.ratings.first.name).to eq(ArchiveConfig.RATING_DEFAULT_TAG_NAME)
      end
      it "Relationships should be empty" do
        expect(@work.relationships).to be_empty
      end
      it "Categories should be empty" do
        expect(@work.categories).to be_empty
      end
      it "Additional Tags should be empty" do
        expect(@work.freeforms).to be_empty
      end
      it "Notes should be empty" do
        expect(@work.notes).to be_empty
      end
      it "Author pseud" do
        expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
      end
    end
  end
end

describe "API v2 WorksController - Unit Tests", type: :request do
  before do
    @under_test = Api::V2::WorksController.new
  end

  it "work_url_from_external returns an error message when the work URL is blank" do
    work_url_response = @under_test.instance_eval { find_work_by_import_url("") }
    expect(work_url_response[:message]).to eq "Please provide the original URL for the work."
  end

  it "work_url_from_external returns the work url when a work is found" do
    work1 = create(:work, imported_from_url: "http://foo")
    work_url_response = @under_test.instance_eval { find_work_by_import_url("http://foo") }
    expect(work_url_response[:works].first).to eq work1
  end

  it "notify_and_return_authors calls find_or_invite on each external author" do
    user = create(:user)
    author1 = create(:external_author)
    author2 = create(:external_author)
    work = create(:work)
    name1 = create(:external_author_name, name: 'n1', external_author: author1)
    name2 = create(:external_author_name, name: 'n2', external_author: author2)
    create(:external_creatorship, external_author_name: name1, creation: work)
    create(:external_creatorship, external_author_name: name2, creation: work)

    @under_test.instance_eval { notify_and_return_authors([work], user) }
    emails = Invitation.all.map(&:invitee_email)
    expect(emails).to include(author1.email)
    expect(emails).to include(author2.email)
  end

  describe "work_errors" do
    it "returns an error if a work doesn't contain chapter urls" do
      work = { chapter_urls: [] }
      error_message = @under_test.instance_eval { work_errors(work) }
      expect(error_message[1][0]).to start_with "This work doesn't contain any chapter URLs."
    end

    it "returns an error if a work has too many chapters" do
      loads_of_items = Array.new(210) { |_| "chapter_url" }
      work = { chapter_urls: loads_of_items }
      error_message = @under_test.instance_eval { work_errors(work) }
      expect(error_message[1][0]).to start_with "This work contains too many chapter URLs"
    end
  end
end
