#!/usr/bin/env ruby

# frozen_string_literal: true

require 'active_support/core_ext/object/to_query'
require 'optparse'
require 'open3'
require 'rainbow/refinement'
using Rainbow

module Secpick
  BRANCH_PREFIX = 'security'
  STABLE_SUFFIX = 'stable'

  DEFAULT_REMOTE = 'security'

  SECURITY_MR_URL = 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/new'

  class SecurityFix
    def initialize
      @options = self.class.options
    end

    def dry_run?
      @options[:try] == true
    end

    def source_branch
      branch = "#{@options[:branch]}-#{@options[:version]}"
      branch = "#{BRANCH_PREFIX}-#{branch}" unless branch.start_with?("#{BRANCH_PREFIX}-")
      branch
    end

    def stable_branch
      "#{@options[:version]}-#{STABLE_SUFFIX}-ee"
    end

    def git_commands
      [
        fetch_stable_branch,
        create_backport_branch,
        cherry_pick_commit,
        push_to_remote,
        checkout_original_branch
      ]
    end

    def gitlab_params
      {
        issuable_template: 'Security Release',
        merge_request: {
          source_branch: source_branch,
          target_branch: stable_branch
        }
      }
    end

    def new_mr_url
      SECURITY_MR_URL
    end

    def create!
      if dry_run?
        puts "\nGit commands:".blue
        puts git_commands.join("\n")

        if !@options[:merge_request]
          puts "\nMerge request URL:".blue
          puts new_mr_url
        end

        puts "\nMerge request params:".blue
        pp gitlab_params
      else
        cmd = git_commands.join(' && ')
        stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)

        puts stdout.read&.green
        puts stderr.read&.red

        if wait_thr.value.success? && !@options[:merge_request]
          puts "#{new_mr_url}?#{gitlab_params.to_query}".blue
        end

        stdin.close
        stdout.close
        stderr.close
      end
    end

    def self.options
      { version: nil, branch: nil, sha: nil, merge_request: false }.tap do |options|
        parser = OptionParser.new do |opts|
          opts.banner = "Usage: #{$0} [options]"
          opts.on('-v', '--version 10.0', 'Version') do |version|
            options[:version] = version&.tr('.', '-')
          end

          opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch|
            options[:branch] = branch
          end

          opts.on('-s', '--sha abcd', 'SHA or SHA range to cherry pick (optional, defaults to current)') do |sha|
            options[:sha] = sha
          end

          opts.on('-r', '--remote dev', "Git remote name of security repo (optional, defaults to `#{DEFAULT_REMOTE}`)") do |remote|
            options[:remote] = remote
          end

          opts.on('--mr', '--merge-request', 'Create relevant security Merge Request targeting the stable branch') do
            options[:merge_request] = true
          end

          opts.on('-d', '--dry-run', 'Only show Git commands, without calling them') do
            options[:try] = true
          end

          opts.on('-h', '--help', 'Displays Help') do
            puts opts

            exit
          end
        end

        parser.parse!

        options[:sha] ||= `git rev-parse HEAD`.strip
        options[:branch] ||= `git rev-parse --abbrev-ref HEAD`.strip
        options[:remote] ||= DEFAULT_REMOTE

        nil_options = options.select {|_, v| v.nil? }
        unless nil_options.empty?
          abort("Missing: #{nil_options.keys.join(', ')}. Use #{$0} --help to see the list of options available".red)
        end

        abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
      end
    end

    private

    def checkout_original_branch
      "git checkout #{@options[:branch]}"
    end

    def push_to_remote
      [
        "git push #{@options[:remote]} #{source_branch} --no-verify",
        *merge_request_push_options
      ].join(' ')
    end

    def merge_request_push_options
      return [] unless @options[:merge_request]

      [
        "-o mr.create",
        "-o mr.target='#{stable_branch}'",
        "-o mr.description='Please apply Security Release template. /milestone %#{milestone}'"
      ]
    end

    def cherry_pick_commit
      "git cherry-pick #{@options[:sha]}"
    end

    def create_backport_branch
      "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch} --no-track"
    end

    def fetch_stable_branch
      "git fetch #{@options[:remote]} #{stable_branch}"
    end

    def milestone
      @options[:version].gsub('-', '.')
    end
  end
end

Secpick::SecurityFix.new.create!