# frozen_string_literal: true

def run(*args)
  out, err, status = Open3.capture3(*args)
  raise "Command failed: #{args.inspect}\n#{out}\n#{err}" unless status.success?
  out
end

def fake_version_rb(version)
  File.read("#{Rails.root}/lib/version.rb").sub(/STRING = ".*"/, "STRING = \"#{version}\"")
end

RSpec.describe "tasks/version_bump" do
  let(:tmpdir) { Dir.mktmpdir }
  let(:origin_path) { "#{tmpdir}/origin-repo" }
  let(:local_path) { "#{tmpdir}/local-repo" }

  before do
    ENV["RUNNING_VERSION_BUMP_IN_RSPEC_TESTS"] = "1"

    Rake::Task.clear
    Discourse::Application.load_tasks

    FileUtils.mkdir_p origin_path

    Dir.chdir(origin_path) do
      FileUtils.mkdir_p "lib"
      FileUtils.mkdir_p "tmp"

      File.write(".gitignore", "tmp\n")
      File.write("lib/version.rb", fake_version_rb("3.2.0.beta1-dev"))

      run "git", "init"
      run "git", "checkout", "-b", "main"
      run "git", "add", "."
      run "git", "commit", "-m", "Initial commit"

      run "git", "checkout", "-b", "stable"
      File.write("#{origin_path}/lib/version.rb", fake_version_rb("3.1.2"))
      run "git", "add", "."
      run "git", "commit", "-m", "Previous stable version bump"

      run "git", "checkout", "main"
      run "git", "config", "receive.denyCurrentBranch", "ignore"
    end

    run "git", "clone", "-b", "main", origin_path, local_path
  end

  after do
    FileUtils.remove_entry(tmpdir)
    ENV.delete("RUNNING_VERSION_BUMP_IN_RSPEC_TESTS")
  end

  it "can bump the beta version with version_bump:beta" do
    Dir.chdir(local_path) { capture_stdout { invoke_rake_task("version_bump:beta") } }

    Dir.chdir(origin_path) do
      # Commits are present with correct messages
      expect(run("git", "log", "--pretty=%s").lines.map(&:strip)).to eq(
        ["Bump version to v3.2.0.beta2-dev", "Bump version to v3.2.0.beta1", "Initial commit"],
      )

      # Expected tags present
      expect(run("git", "tag").lines.map(&:strip)).to contain_exactly(
        "latest-release",
        "beta",
        "v3.2.0.beta1",
      )

      # Tags are all present and attached to the correct commit
      %w[latest-release beta v3.2.0.beta1].each do |tag_name|
        expect(run("git", "log", "--pretty=%s", "-1", tag_name).strip).to eq(
          "Bump version to v3.2.0.beta1",
        )
      end

      # Version numbers in version.rb are correct at all commits
      expect(run "git", "show", "HEAD", "lib/version.rb").to include('STRING = "3.2.0.beta2-dev"')
      expect(run "git", "show", "HEAD~1", "lib/version.rb").to include('STRING = "3.2.0.beta1"')
      expect(run "git", "show", "HEAD~2", "lib/version.rb").to include('STRING = "3.2.0.beta1-dev"')
    end
  end

  it "can perform a minor stable bump with version_bump:minor_stable" do
    Dir.chdir(local_path) { capture_stdout { invoke_rake_task("version_bump:minor_stable") } }

    Dir.chdir(origin_path) do
      # No commits on main branch
      expect(run("git", "log", "--pretty=%s").lines.map(&:strip)).to eq(["Initial commit"])

      # Expected tags present
      expect(run("git", "tag").lines.map(&:strip)).to eq(["v3.1.3"])

      run "git", "checkout", "stable"

      # Correct commits on stable branch
      expect(run("git", "log", "--pretty=%s").lines.map(&:strip)).to eq(
        ["Bump version to v3.1.3", "Previous stable version bump", "Initial commit"],
      )

      # Tag points to correct commit
      expect(run("git", "log", "--pretty=%s", "-1", "v3.1.3").strip).to eq("Bump version to v3.1.3")

      # Version numbers in version.rb are correct at all commits
      expect(run "git", "show", "HEAD", "lib/version.rb").to include('STRING = "3.1.3"')
      expect(run "git", "show", "HEAD~1", "lib/version.rb").to include('STRING = "3.1.2"')
    end
  end

  it "can prepare a major stable bump with version_bump:major_stable_prepare" do
    Dir.chdir(local_path) do
      capture_stdout { invoke_rake_task("version_bump:major_stable_prepare", "3.3.0") }
    end

    Dir.chdir(origin_path) do
      # Commits are present with correct messages
      expect(run("git", "log", "--pretty=%s").lines.map(&:strip)).to eq(
        ["Bump version to v3.3.0.beta1-dev", "Bump version to v3.2.0.beta1", "Initial commit"],
      )

      # Expected tags present
      expect(run("git", "tag").lines.map(&:strip)).to contain_exactly(
        "latest-release",
        "beta",
        "v3.2.0.beta1",
      )

      # Tags are all present and attached to the correct commit
      %w[latest-release beta v3.2.0.beta1].each do |tag_name|
        expect(run("git", "log", "--pretty=%s", "-1", tag_name).strip).to eq(
          "Bump version to v3.2.0.beta1",
        )
      end

      # Version numbers in version.rb are correct at all commits
      expect(run "git", "show", "HEAD:lib/version.rb").to include('STRING = "3.3.0.beta1-dev"')
      expect(run "git", "show", "HEAD~1:lib/version.rb").to include('STRING = "3.2.0.beta1"')
      expect(run "git", "show", "HEAD~2:lib/version.rb").to include('STRING = "3.2.0.beta1-dev"')

      # No changes to stable branch
      expect(run("git", "log", "--pretty=%s", "stable").lines.map(&:strip)).to eq(
        ["Previous stable version bump", "Initial commit"],
      )
    end
  end

  it "can merge a stable release commit into the stable branch with version_bump:major_stable_merge" do
    Dir.chdir(local_path) do
      # Prepare first, and find sha1 in output
      output = capture_stdout { invoke_rake_task("version_bump:major_stable_prepare", "3.3.0") }
      stable_bump_commit = output[/major_stable_merge\[(.*)\]/, 1]
      capture_stdout { invoke_rake_task("version_bump:major_stable_merge", stable_bump_commit) }
    end

    Dir.chdir(origin_path) do
      # Commits on stable branch are present with correct messages
      expect(run("git", "log", "--pretty=%s", "stable").lines.map(&:strip)).to contain_exactly(
        "Merge v3.2.0.beta1 into stable",
        "Bump version to v3.2.0",
        "Previous stable version bump",
        "Bump version to v3.2.0.beta1",
        "Initial commit",
      )

      # Most recent commit is the version bump
      expect(run("git", "log", "--pretty=%s", "-1", "stable").strip).to eq("Bump version to v3.2.0")

      # Second most recent commit is a merge commit
      parents = run("git", "log", "--pretty=%P", "-n", "1", "stable~1").split(/\s+/).map(&:strip)
      expect(parents.length).to eq(2)

      # With correct parents
      parent_commit_messages = parents.map { |p| run("git", "log", "--pretty=%s", "-1", p).strip }
      expect(parent_commit_messages).to contain_exactly(
        "Bump version to v3.2.0.beta1",
        "Previous stable version bump",
      )

      # Tag is applied to stable version bump commit
      expect(run("git", "log", "--pretty=%s", "-1", "v3.2.0").strip).to eq("Bump version to v3.2.0")
    end
  end

  it "can stage a PR of multiple security fixes using version_bump:stage_security_fixes" do
    Dir.chdir(origin_path) do
      run "git", "checkout", "-b", "security-fix-one"

      File.write("firstfile.txt", "contents")
      run "git", "add", "firstfile.txt"
      run "git", "commit", "-m", "security fix one, commit one"
      File.write("secondfile.txt", "contents")
      run "git", "add", "secondfile.txt"
      run "git", "commit", "-m", "security fix one, commit two"

      run "git", "checkout", "main"
      run "git", "checkout", "-b", "security-fix-two"
      File.write("somefile.txt", "contents")
      run "git", "add", "somefile.txt"
      run "git", "commit", "-m", "security fix two"
    end

    Dir.chdir(local_path) do
      output =
        capture_stdout do
          ENV["SECURITY_FIX_REFS"] = "origin/security-fix-one,origin/security-fix-two"
          invoke_rake_task("version_bump:stage_security_fixes", "main")
        ensure
          ENV.delete("SECURITY_FIX_REFS")
        end
    end

    Dir.chdir(origin_path) do
      # Check each fix has been added as a single commit, with the message matching the first commit on the branch
      expect(run("git", "log", "--pretty=%s", "main").lines.map(&:strip)).to eq(
        [
          "security fix two",
          "security fix one, commit two",
          "security fix one, commit one",
          "Initial commit",
        ],
      )

      # Check all the files from both fixes are present
      expect(run("git", "show", "main:somefile.txt")).to eq("contents")
      expect(run("git", "show", "main:firstfile.txt")).to eq("contents")
      expect(run("git", "show", "main:secondfile.txt")).to eq("contents")
    end
  end
end