# frozen_string_literal: true

require 'set'

module GitlabQuality
  module TestTooling
    module Report
      class ReportAsIssue
        include Concerns::Utils

        def initialize(token:, input_files:, related_issues_file: nil, project: nil, confidential: false, dry_run: false, **_kwargs)
          @project = project
          @gitlab = (dry_run ? GitlabIssueDryClient : GitlabIssueClient).new(token: token, project: project)
          @files = Array(input_files)
          @confidential = confidential
          @issue_logger = IssueLogger.new(file_path: related_issues_file) unless related_issues_file.nil?
        end

        def invoke!
          validate_input!

          run!
        end

        private

        attr_reader :gitlab, :files, :project, :issue_type, :confidential, :issue_logger

        def run!
          raise NotImplementedError
        end

        def collect_issues(test, issues)
          issue_logger&.collect(test, issues)
        end

        def write_issues_log_file
          issue_logger&.write
        end

        def test_hash(test)
          OpenSSL::Digest.hexdigest('SHA256', "#{test.file}#{test.name}")
        end

        def new_issue_description(test)
          <<~DESCRIPTION
          ### Test metadata (don't modify)

          | Field | Value |
          | ------ | ------ |
          | File | #{test.test_file_link} |
          | Description | `#{test.name}` |
          | Test level | #{test.level} |
          | Hash | `#{test_hash(test)}` |
          | Duration | #{test.run_time} seconds |
          | Expected duration | < #{test.max_duration_for_test} seconds |
          #{"| Test case | #{test.testcase} |" if test.testcase}
          DESCRIPTION
        end

        def new_issue_labels(_test)
          []
        end

        def new_issue_assignee_id(_test)
          nil
        end

        def new_issue_due_date(_test)
          nil
        end

        def validate_input!
          assert_project!
          assert_input_files!(files)
          gitlab.assert_user_permission!
        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

        def create_issue(test)
          attrs = {
            title: title_from_test(test),
            description: new_issue_description(test),
            labels: new_issue_labels(test).to_a,
            issue_type: issue_type,
            assignee_id: new_issue_assignee_id(test),
            due_date: new_issue_due_date(test),
            confidential: confidential
          }.compact
          issue = gitlab.create_issue(**attrs)

          new_link = issue_type == 'test_case' ? issue&.web_url&.sub('/issues/', '/quality/test_cases/') : issue&.web_url

          puts "Created new #{issue_type}: #{new_link}"

          issue
        end

        def issue_labels(issue)
          issue&.labels&.to_set || Set.new
        end

        def update_labels(issue, test, new_labels = Set.new)
          labels = up_to_date_labels(test: test, issue: issue, new_labels: new_labels)

          return if issue_labels(issue) == labels

          gitlab.edit_issue(iid: issue.iid, options: { labels: labels.to_a })
        end

        def up_to_date_labels(test:, issue: nil, new_labels: Set.new)
          labels = issue_labels(issue)
          labels |= new_labels.to_set
          ee_test?(test) ? labels << 'Enterprise Edition' : labels.delete('Enterprise Edition')

          if test.quarantine?
            labels << 'quarantine'
            labels << "quarantine::#{test.quarantine_type}"
          else
            labels.delete_if { |label| label.include?('quarantine') }
          end

          labels
        end

        def find_issues(test, labels, state: nil)
          search_options = { labels: labels.to_a }
          search_options[:state] = state if state

          gitlab.find_issues(options: search_options).find_all do |issue|
            issue_title = issue.title.strip
            test_file_path_found = !test.file.to_s.empty? && issue_title.include?(partial_file_path(test.file))
            issue_title.include?(test.name) || test_file_path_found
          end
        end

        def pipeline_name_label
          case pipeline
          when 'production'
            'found:gitlab.com'
          when 'canary', 'staging'
            "found:#{pipeline}.gitlab.com"
          when 'staging-canary'
            "found:canary.staging.gitlab.com"
          when 'preprod'
            'found:pre.gitlab.com'
          when 'nightly', Runtime::Env.default_branch, 'staging-ref', 'release'
            "found:#{pipeline}"
          when 'customers-gitlab-com'
            'found:customers.stg.gitlab.com'
          else
            raise "No `found:*` label for the `#{pipeline}` pipeline!"
          end
        end

        def ee_test?(test)
          test.file =~ %r{features/ee/(api|browser_ui)}
        end
      end
    end
  end
end