# frozen_string_literal: true

module GitlabQuality
  module TestTooling
    module Report
      class MergeRequestSlowTestsReport
        SLOW_TEST_MESSAGE = '<!-- slow-test -->'
        SLOW_TEST_LABEL = '/label ~"rspec:slow test detected"'
        SLOW_TEST_NOTE_SOURCE_CODE = 'Generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling/-/blob/main/lib/gitlab_quality/test_tooling/report/merge_request_slow_tests_report.rb).'

        def initialize(token:, input_files:, merge_request_iid:, project: nil, dry_run: false, **_kwargs)
          @project = project
          @gitlab_merge_request = (dry_run ? GitlabClient::MergeRequestsDryClient : GitlabClient::MergeRequestsClient).new(token: token, project: project)
          @files = Array(input_files)
          @merge_request_iid = merge_request_iid
          @slow_tests = []
        end

        def invoke!
          validate_input!

          run!
        end

        private

        attr_reader :gitlab_merge_request, :files, :project, :merge_request_iid, :slow_tests

        def run!
          puts "Reporting slow tests in MR #{merge_request_iid}"

          TestResults::Builder.new(files).test_results_per_file do |test_results|
            puts "=> Reporting #{test_results.count} tests in #{test_results.path}"

            @slow_tests += slow_related_tests(find_slow_tests(test_results))
          end

          @slow_tests.uniq!
          @slow_tests.reject!(&:failed?)

          return unless @slow_tests.any?

          upsert_mr_note(@slow_tests)
        end

        def related_test?(slow_test)
          base_file = slow_test.file.split('/').last.gsub('_spec.rb', '')

          merge_request_changed_files.any? { |change| change =~ /#{base_file}/ }
        end

        def slow_related_tests(slow_test_results)
          slow_test_results.select do |slow_test|
            related_test?(slow_test)
          end
        end

        def merge_request_changed_files
          @merge_request_changed_files ||= gitlab_merge_request.merge_request_changed_files(merge_request_iid: merge_request_iid)
        end

        def find_slow_tests(test_results)
          test_results.select(&:slow_test?)
        end

        def note_header
          [
            SLOW_TEST_MESSAGE,
            SLOW_TEST_LABEL,
            ":tools: #{SLOW_TEST_NOTE_SOURCE_CODE}\n",
            "---\n",
            ":snail: Slow tests detected in this merge request. These slow tests might be related to this merge request's changes.",
            "<details><summary>Click to expand</summary>\n",
            '| Job | File | Name | Duration | Expected duration |',
            '| --- | --- | --- | --- | --- |'
          ]
        end

        def slow_test_rows(slow_test)
          rows = []

          slow_test.each do |test|
            rows << slow_test_table_row(test)
          end

          rows
        end

        def build_note(slow_test)
          rows = note_header + slow_test_rows(slow_test) + ["\n</details>"]

          rows.join("\n")
        end

        def note_comment_includes_slow_test?(gitlab_note, slow_test)
          gitlab_note.include?("[##{slow_test.ci_job_id}](#{slow_test.ci_job_url}) | #{slow_test.test_file_link}")
        end

        def slow_test_table_row(slow_test)
          row = [
            "[##{slow_test.ci_job_id}](#{slow_test.ci_job_url})",
            slow_test.test_file_link,
            slow_test.name,
            "#{slow_test.run_time} s",
            "< #{slow_test.max_duration_for_test} s"
          ]

          "| #{row.join(' | ')} |"
        end

        def add_slow_test_rows(gitlab_note, slow_tests)
          gitlab_note.gsub!("\n\n</details>", "")

          slow_tests.each do |slow_test|
            gitlab_note += "\n#{slow_test_table_row(slow_test)}" unless note_comment_includes_slow_test?(gitlab_note, slow_test)
          end

          "#{gitlab_note}\n\n</details>"
        end

        def upsert_mr_note(slow_tests) # rubocop:disable Metrics/AbcSize
          existing_note = gitlab_merge_request.find_note(body: SLOW_TEST_MESSAGE, merge_request_iid: merge_request_iid)

          if existing_note
            puts "Update note for merge request: #{merge_request_iid}"

            up_to_date_note = add_slow_test_rows(existing_note.body, slow_tests)

            gitlab_merge_request.update_note(id: existing_note['id'], note: up_to_date_note, merge_request_iid: merge_request_iid) if existing_note.body != up_to_date_note
          else
            up_to_date_note = build_note(slow_tests)

            puts "Create note for merge request: #{merge_request_iid}"

            gitlab_merge_request.create_note(note: up_to_date_note, merge_request_iid: merge_request_iid)
          end
        end

        def validate_input!
          assert_project!
          assert_input_files!(files)
        end

        def assert_project!
          return if project

          abort "Please provide a valid project ID or path with the `-p/--project` option!"
        end

        def assert_input_files!(files)
          return if Dir.glob(files).any?

          abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`"
        end
      end
    end
  end
end