require "rails_helper"

describe GenerateContractsZipJob do
  let(:project) { create(:project) }
  let(:download) { create(:download, project: project) }
  let(:contract_template) { create(:contract_template, :project) }

  before do
    create(:appearance_release_with_contract_template, :native, project: project, person_name: "John Doe")
    create(:appearance_release_with_contract_template, :non_native, project: project)
    dir = Rails.root.join("spec", "fixtures", "files")
    files = ["contract.pdf", "AppearanceRelease.pdf"]
    # Attachments in the test environment do not persist to cloud storage
    # Therefore we want to stub calls to `open` with a cloud storage URL
    allow_any_instance_of(ReleaseContractCollectionService).to receive(:open).and_return(StringIO.new("file data"))
    allow_any_instance_of(ReleaseContractCollectionService).to receive(:build).and_yield(dir, files)
  end

  describe ".perform_later" do
    it "enqueues a background job for generating zip file" do
      expect {
        GenerateContractsZipJob.perform_later(project, download, "AppearanceRelease", project.appearance_releases.ids, '', '')
      }.to have_enqueued_job
    end
  end

  describe ".perform_now" do
    shared_examples "generates ZIP containig CSV file with all releases data" do
      it "generates ZIP containing CSV file with all releases data for all release types" do
        lowercase_plural = subject.constantize.model_name.plural
        GenerateContractsZipJob.perform_now(project, download, subject, project.public_send(lowercase_plural).ids, '', '')

        generated_zip = download.file.blob.download
        csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv"
        Zip::InputStream.open(StringIO.new(generated_zip)) do |io|
          while entry = io.get_next_entry
            next unless entry.name == csv_file_name

            csv_file = entry.get_input_stream.read

            release_class = Object.const_get subject
            release_headers = release_class.csv_headers

            release_headers.each do |header|
              expect(csv_file).to match header
              expect(csv_file).not_to match translation_missing
            end
          end
        end
      end
    end

    shared_examples "generates ZIP containig CSV file with specific releases data" do
      it "generates ZIP containing CSV file with all selected releases data for selected releases" do
        lowercase_plural = subject.constantize.model_name.plural
        all_releases = project.public_send(lowercase_plural)
        included_releases = all_releases.where(id: all_releases.ids[0..1])
        not_included_releases = all_releases.where.not(id: all_releases.ids[0..1])
        GenerateContractsZipJob.perform_now(project, download, subject, included_releases.ids, '', '')

        generated_zip = download.file.blob.download
        csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv"
        Zip::InputStream.open(StringIO.new(generated_zip)) do |io|
          while entry = io.get_next_entry
            next unless entry.name == csv_file_name

            csv_file = entry.get_input_stream.read

            release_class = Object.const_get subject
            release_headers = release_class.csv_headers

            release_headers.each do |header|
              expect(csv_file).to match header
              expect(csv_file).not_to match translation_missing
            end

            included_releases.each do |release|
              expect(csv_file).to match release.person_first_name
            end

            not_included_releases.each do |release|
              expect(csv_file).not_to match release.person_first_name
            end
          end
        end
      end

      it "generates ZIP containing CSV file with all filtered releases data for filtered releases" do
        lowercase_plural = subject.constantize.model_name.plural
        GenerateContractsZipJob.perform_now(project, download, subject, [], '', 'complete')

        complete_releases = project.public_send(lowercase_plural).complete
        incomplete_releases = project.public_send(lowercase_plural).incomplete

        generated_zip = download.file.blob.download
        csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv"
        Zip::InputStream.open(StringIO.new(generated_zip)) do |io|
          while entry = io.get_next_entry
            next unless entry.name == csv_file_name

            csv_file = entry.get_input_stream.read

            release_class = Object.const_get subject
            release_headers = release_class.csv_headers

            release_headers.each do |header|
              expect(csv_file).to match header
              expect(csv_file).not_to match translation_missing
            end

            complete_releases.each do |release|
              expect(csv_file).to match release.person_first_name
            end

            incomplete_releases.each do |release|
              expect(csv_file).not_to match release.person_first_name
            end
          end
        end
      end

      it "generates ZIP containing CSV file with all search query matching releases" do
        lowercase_plural = subject.constantize.model_name.plural
        matched_releases = project.public_send(lowercase_plural).search('Brad')
        not_matched_releases = project.public_send(lowercase_plural).where.not(id: matched_releases.ids)
        GenerateContractsZipJob.perform_now(project, download, subject, [], 'Brad', '')

        generated_zip = download.file.blob.download
        csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv"
        Zip::InputStream.open(StringIO.new(generated_zip)) do |io|
          while entry = io.get_next_entry
            next unless entry.name == csv_file_name

            csv_file = entry.get_input_stream.read

            release_class = Object.const_get subject
            release_headers = release_class.csv_headers

            release_headers.each do |header|
              expect(csv_file).to match header
              expect(csv_file).not_to match translation_missing
            end

            matched_releases.each do |release|
              expect(csv_file).to match release.person_first_name
            end

            not_matched_releases.each do |release|
              expect(csv_file).not_to match release.person_first_name
            end
          end
        end
      end
    end

    it "updates a download record and creates attachment for it" do
      GenerateContractsZipJob.perform_now(project, download, "AppearanceRelease", project.appearance_releases.ids, '', '')

      expect(download.project).to eq project
      expect(download.release_type).to eq "AppearanceRelease"
      expect(download.name).to eq "my-video-project_appearance-releases"
      expect(download.status).to eq "success"
      expect(download.file).to be_attached
    end

    context "generates ZIP for acquired media releases" do
      let(:release) { create(:acquired_media_release_with_contract_template, :native, project: project) }
      subject { 'AcquiredMediaRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
    end

    context "generates ZIP for appearance releases" do
      let(:release) { create(:appearance_release_with_contract_template, :native, project: project, person_name: "John Doe") }
      let(:incomplete_release) { create(:appearance_release_with_contract_template, project: project, person_name: "Jane Doe") }
      let(:complete_release) { create(:appearance_release_with_contract_template, :non_native, project: project, person_name: "Brad Doe") }
      subject { 'AppearanceRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
      it_behaves_like "generates ZIP containig CSV file with specific releases data"
    end

    context "generates ZIP for location releases" do
      let(:release) { create(:location_release_with_contract_template, :native, project: project) }
      subject { 'LocationRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
    end

    context "generates ZIP for material releases" do
      let(:release) { create(:material_release_with_contract_template, :native, project: project) }
      subject { 'MaterialRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
    end

    context "generates ZIP for medical releases" do
      let(:release) { create(:medical_release_with_contract_template, :native, project: project) }
      subject { 'MedicalRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
    end

    context "generates ZIP for misc releases" do
      let(:release) { create(:misc_release_with_contract_template, :native, project: project) }
      subject { 'MiscRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
    end

    context "generates ZIP for music releases" do
      let(:release) { create(:music_release_with_contract_template, project: project) }
      subject { 'MusicRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
    end

    context "generates ZIP for talent releases" do
      let(:release) { create(:talent_release_with_contract_template, :native, project: project) }
      subject { 'TalentRelease' }

      it_behaves_like "generates ZIP containig CSV file with all releases data"
    end

    context "When there are errors" do
      let(:error) { StandardError.new("Contracts or contract templates not found.") }

      before do
        allow(ProjectsChannel).to receive(:broadcast_download_generation_update).with(download, I18n.t("contract_downloads.download.failure"))
        allow_any_instance_of(ReleaseContractCollectionService).to receive(:build).and_raise(StandardError, "Contracts or contract templates not found")
      end

      it "updates status to 'failure' and sends user a notification" do
        GenerateContractsZipJob.perform_now(project, download, "AppearanceRelease", project.appearance_releases.ids, '', '')

        expect(download.status).to eq "failure"
        expect(ProjectsChannel).to have_received(:broadcast_download_generation_update).with(download, I18n.t("contract_downloads.download.failure"))
      end
    end
  end

  after do
    # Delete the file created in fixture.
    # Or the tests will fail on next run due to already existing files in existing zip.
    path = Rails.root.join("spec", "fixtures", "files")
    releases = %w[acquired-media appearance location material medical misc music talent]
    releases.each do |release|
      if File.exists? "#{path}/my-video-project_#{release}-releases.zip"
        File.delete("#{path}/my-video-project_#{release}-releases.zip")
      end
    end
  end

  private
  def translation_missing
    /translation missing/
  end
end
