# frozen_string_literal: true

module ReleaseTools
  module PatchRelease
    class BlogMergeRequest < MergeRequest
      include ::SemanticLogger::Loggable
      include ReleaseTools::Security::IssueHelper
      include ReleaseTools::Security::MergeRequestHelper

      # Projects that are not included in the table of fixes because their
      # fixes are often due to external CVEs
      NON_TABLE_OF_FIXES_PROJECTS = [
        Project::OmnibusGitlab.security_id,
        Project::CNGImage.security_id
      ].freeze

      def project
        ReleaseTools::Project::WWWGitlabCom
      end

      def labels
        'patch release post'
      end

      def title
        "Draft: Adding #{versions_str} blog post"
      end

      def assignee_ids
        if security_fixes?
          appsec_release_managers.collect(&:id)
        else
          release_managers.collect(&:id)
        end
      end

      def source_branch
        "create-#{hyphenated_version}-post"
      end

      def create
        create_blog_post_file

        return if SharedStatus.dry_run?

        super
      end

      def patch_issue_url
        return 'test.gitlab.com' if SharedStatus.dry_run?
        return unless patch_coordinator.single_version?

        Issue.new(version: versions.first).url
      end

      def generate_blog_content
        ERB.new(content_template_path, trim_mode: '-').result(binding)
      end

      def blog_post_filename
        if security_fixes?
          "#{Date.current + 1.day}-#{release_name_hyphen}-gitlab-#{hyphenated_version}-released.html.md"
        else
          "#{Date.current}-gitlab-#{hyphenated_version}-released.html.md"
        end
      end

      def metadata_canonical_path
        return if patch_coordinator.single_version?

        date = Time.now.utc + 1.day

        "/releases/#{date.strftime('%Y/%m/%d')}/#{release_name_hyphen}-gitlab-#{hyphenated_version}-released/"
      end

      def security_issue_header(security_issue)
        security_issue&.cves_issue&.title || security_issue.issue.title
      end

      def security_issue_blog_content(security_issue)
        cves_issue = security_issue.cves_issue

        if cves_issue && cves_issue.valid_yaml?
          <<~STR
            #{cves_issue.vulnerability_description.gsub(/[.]+\z/, '')}.
            This is a #{cves_issue.cvss_severity.downcase} severity issue ([`#{cves_issue.cvss_string}`](https://gitlab-com.gitlab.io/gl-security/product-security/appsec/cvss-calculator/explain#explain=#{cves_issue.cvss_string}), #{cves_issue.cvss_base_score}).
            #{cves_id_link(cves_issue)}

            #{cves_issue.credit.gsub(/[.]+\z/, '')}.
          STR
        else
          logger.warn "The content of the security issue (#{security_issue.web_url}) was skipped due to invalid CVES YAML or because of missing CVES."
          <<~STR
            TODO: add description for this issue
          STR
        end
      end

      def cves_id_link(cves_issue)
        if cves_issue.cve_id.blank?
          "We have requested a CVE ID and will update this blog post when it is assigned."
        else
          "It is now mitigated in the latest release and is assigned [#{cves_issue.cve_id}](https://cve.mitre.org/cgi-bin/cvename.cgi?name=#{cves_issue.cve_id})."
        end
      end

      def project_path
        # If the blog post contains security content, the blog MR needs
        # to be created in the security mirror.
        return project.security_path if security_fixes?

        super
      end

      def sorted_security_content
        security_fixes
          .sort_by do |issue|
            if issue.cves_issue&.valid_yaml?
              issue.cves_issue&.cvss_base_score || -1
            else
              -1
            end
          end
          .reverse
      end

      def sorted_table_content
        sorted_security_content.reject do |issue|
          NON_TABLE_OF_FIXES_PROJECTS.include?(issue.project_id)
        end
      end

      def security_issue_slug(security_issue)
        security_issue_header(security_issue)
          .downcase
          .scan(/\w|\d| |-/)
          .join
          .tr(' ', '-')
      end

      def displayed_severity(security_issue)
        return 'TODO' unless security_issue.cves_issue&.valid_yaml?

        severity = security_issue.cves_issue&.cvss_severity
        return 'TODO' if severity == 'None'

        severity
      end

      def blog_metadata_title
        "GitLab #{title_header} #{release_name}: #{versions_str}".squish
      end

      def blog_metadata_description
        "Learn more about GitLab #{title_header} #{release_name}: #{versions_str} for GitLab Community Edition (CE) and Enterprise Edition (EE).".squish
      end

      def blog_metadata_tags
        'security' unless patch_coordinator.single_version?
      end

      def exists?
        if security_fixes?
          security_blog_merge_request.present?
        else
          super
        end
      end

      def bug_fixes
        patch_coordinator.merge_requests(with_patch_version: true)
      end

      def security_fixes?
        security_fixes.present?
      end

      def bug_fixes?
        patch_coordinator.pressure.positive?
      end

      protected

      def release_name
        'Patch Release'
      end

      def release_name_hyphen
        release_name.downcase.tr(' ', '-')
      end

      def hyphenated_version
        versions.first.tr(', ', '--').tr('.', '-')
      end

      def template_path
        File.expand_path('../../../templates/patch_blog_post_merge_request_description.md.erb', __dir__)
      end

      def content_template_path
        file_name = if patch_coordinator.single_version?
                      'patch_release_single_version_template'
                    else
                      'patch_release_template'
                    end

        File.read("templates/blog_posts/#{file_name}.html.md.erb")
      end

      def create_blog_post_file
        file_content = generate_blog_content
        filepath = blog_post_filepath + blog_post_filename

        logger.info('Created blog post file')

        unless SharedStatus.dry_run?
          Retriable.with_context(:api) do
            GitlabClient.find_or_create_branch(source_branch, target_branch, project_path)
          end
        end

        logger.info('Created branch', branch_name: source_branch, project: project_path)

        commit(file_content, filepath)
      end

      def commit(file_content, filepath, action = 'create', message = nil)
        message ||= "Adding #{versions_str} blog post"

        actions = [{
          action: action,
          file_path: filepath,
          content: file_content
        }]

        logger.info('Committing blog content', project: project_path, branch: source_branch)

        return if SharedStatus.dry_run?

        Retriable.with_context(:api) do
          GitlabClient.create_commit(
            project_path,
            source_branch,
            message,
            actions
          )
        end
      end

      def blog_post_filepath
        "sites/uncategorized/source/releases/posts/"
      end

      def release_managers
        schedule.active_release_managers
      rescue ReleaseManagers::Schedule::VersionNotFoundError
        logger.fatal('Could not find active release managers')
        nil
      end

      def appsec_release_managers
        schedule.active_appsec_release_managers
      rescue ReleaseManagers::Schedule::VersionNotFoundError
        logger.fatal('Could not find active appsec release managers')
        nil
      end

      def schedule
        @schedule ||= ReleaseManagers::Schedule.new
      end

      def versions
        return [bug_fixes.first[:version]] if patch_coordinator.single_version?

        if security_fixes?
          bug_fixes.map { |release| release[:version] }
        else
          bug_fixes.filter_map do |release|
            release[:version] if release[:pressure].positive?
          end
        end
      end

      def versions_str
        versions.join(', ')
      end

      def critical_patch_release?
        ReleaseTools::SharedStatus.critical_patch_release?
      end

      def title_header
        critical_patch_release? ? 'Critical' : ''
      end
    end
  end
end
