# typed: true
# frozen_string_literal: true

require "cmd/shared_examples/args_parse"
require "dev-cmd/bump-formula-pr"
require "utils/pypi"

RSpec.describe Homebrew::DevCmd::BumpFormulaPr do
  subject(:bump_formula_pr) { described_class.new(["test"]) }

  let(:f) do
    formula("test") do
      T.bind(self, T.class_of(Formula))
      url "https://brew.sh/test-1.2.3.tgz"
    end
  end

  it_behaves_like "parseable arguments"

  describe "#run" do
    it "adds updated mirrors as string literals" do
      formula_path = CoreTap.instance.new_formula_path("couchdb")
      formula_path.dirname.mkpath
      formula_path.write <<~RUBY
        class Couchdb < Formula
          url "https://www.apache.org/dyn/closer.lua?path=couchdb/source/3.5.1/apache-couchdb-3.5.1.tar.gz"
          mirror "https://archive.apache.org/dist/couchdb/source/3.5.1/apache-couchdb-3.5.1.tar.gz"
          sha256 "#{"a" * 64}"
        end
      RUBY
      CoreTap.instance.clear_cache
      Formulary.clear_cache
      Formula.clear_cache
      formula = Formulary.from_contents("couchdb", formula_path, formula_path.read)

      resource_path = mktmpdir/"apache-couchdb-3.5.2.tar.gz"
      resource_path.write("couchdb")
      updated_mirror = "https://archive.apache.org/dist/couchdb/source/3.5.2/apache-couchdb-3.5.2.tar.gz"
      command = described_class.new(["--write-only", "--no-audit", "--version=3.5.2", "couchdb"])

      allow(Homebrew).to receive(:install_bundler_gems!)
      allow(CoreTap.instance).to receive_messages(allow_bump?: true, git?: true,
                                                  remote_repository: "Homebrew/homebrew-core")
      allow(command).to receive(:check_new_version)
      allow(command).to receive(:fetch_resource_and_forced_version).and_return([resource_path, false])
      allow(command).to receive_messages(run_audit: false, update_matching_version_resources!: {})
      allow(PyPI).to receive(:update_python_resources!)
      allow(Utils::Tar).to receive(:validate_file).with(resource_path)
      allow(command.args.named).to receive(:to_formulae).and_return([formula])
      allow(Formula).to receive(:[]).with("couchdb").and_return(formula)
      expect_any_instance_of(Utils::AST::FormulaAST)
        .to receive(:add_stable_stanzas_after) do |formula_ast, name, stanzas|
        expect(name).to eq(:url)
        expect(stanzas).to include([:mirror, "mirror #{updated_mirror.inspect}"])
        formula_ast.add_stanzas_after(name, stanzas, parent: formula_ast.stanza(:stable, type: :block_call))
      end

      command.run

      expect(formula_path.read).to include "  mirror #{updated_mirror.inspect}\n  " \
                                           "sha256 #{resource_path.sha256.inspect}\n"
    end
  end

  describe "::check_throttle" do
    let(:tap) { Tap.fetch("test", "tap") }

    let(:f_throttle) do
      formula("throttle-test") do
        T.bind(self, T.class_of(Formula))
        url "https://brew.sh/test-1.2.3.tgz"

        livecheck do
          throttle 5
        end
      end
    end

    let(:f_throttle_days) do
      formula("throttle-days-test") do
        T.bind(self, T.class_of(Formula))
        url "https://brew.sh/test-1.2.3.tgz"

        livecheck do
          throttle days: 1
        end
      end
    end

    let(:f_throttle_rate_and_days) do
      formula("throttle-rate-and-days-test") do
        T.bind(self, T.class_of(Formula))
        url "https://brew.sh/test-1.2.3.tgz"

        livecheck do
          throttle 5, days: 1
        end
      end
    end

    let(:throttle_error) { "Error: throttle-test should only be updated every 5 releases on multiples of 5\n" }
    let(:throttle_days_error) { "Error: throttle-days-test should only be updated every 1 day\n" }
    let(:throttle_rate_days_error) do
      "Error: throttle-rate-and-days-test should only be updated every 5 releases on multiples of 5 or 1 day\n"
    end

    context "when formula is not in a tap" do
      it "outputs nothing" do
        allow(f).to receive(:tap).and_return(nil)

        expect { bump_formula_pr.check_throttle(f, "1.2.4") }.not_to output.to_stderr
      end
    end

    context "when a livecheck throttle value isn't present" do
      it "does not throttle" do
        allow(f).to receive(:tap).and_return(tap)

        expect { bump_formula_pr.check_throttle(f, "1.2.4") }.not_to output.to_stderr
      end
    end

    context "when patch version is a multiple of throttle rate" do
      it "does not throttle" do
        allow(f_throttle).to receive(:tap).and_return(tap)

        expect { bump_formula_pr.check_throttle(f_throttle, "1.2.5") }.not_to output.to_stderr
      end
    end

    context "when patch version is not a multiple of throttle rate" do
      it "throttles version" do
        allow(f_throttle).to receive(:tap).and_return(tap)

        expect do
          bump_formula_pr.check_throttle(f_throttle, "1.2.4")
        rescue SystemExit
          nil
        end.to output(throttle_error).to_stderr
      end
    end

    context "when patch version is not a multiple and throttle days are set" do
      before do
        allow(f_throttle_rate_and_days).to receive(:tap).and_return(tap)
      end

      it "throttles version when throttle interval has not elapsed" do
        allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)

        expect do
          bump_formula_pr.check_throttle(f_throttle_rate_and_days, "1.2.4")
        rescue SystemExit
          nil
        end.to output(throttle_rate_days_error).to_stderr
      end

      it "does not throttle when throttle interval has elapsed" do
        allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)

        expect { bump_formula_pr.check_throttle(f_throttle_rate_and_days, "1.2.4") }.not_to output.to_stderr
      end
    end

    context "when only throttle days is set" do
      before do
        allow(f_throttle_days).to receive(:tap).and_return(tap)
      end

      it "throttles version when throttle interval has not elapsed" do
        allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(false)

        expect do
          bump_formula_pr.check_throttle(f_throttle_days, "1.2.4")
        rescue SystemExit
          next
        end.to output(throttle_days_error).to_stderr
      end

      it "does not throttle when throttle interval has elapsed" do
        allow(Homebrew::Livecheck).to receive(:throttle_interval_elapsed?).and_return(true)

        expect do
          bump_formula_pr.check_throttle(f_throttle_days, "1.2.4")
        end.not_to output.to_stderr
      end
    end
  end

  describe "::update_matching_version_resources!" do
    let(:f) do
      formula("test") do
        T.bind(self, T.class_of(Formula))
        url "https://brew.sh/test-1.2.3.tgz"

        resource "parent" do
          url "https://brew.sh/parent-1.2.3.tar.gz"
          livecheck do
            formula :parent
          end
        end

        resource "no-parent" do
          url "https://brew.sh/no-parent-1.2.3.tar.gz"
        end
      end
    end
    let(:resource) { f.resource("parent") }
    let(:version) { "1.2.4" }

    it "only updates `:parent` resource" do
      expect(bump_formula_pr).to receive(:update_resource_block!).with(f, resource, version).and_return(:success)
      expect(bump_formula_pr.update_matching_version_resources!(f, version:)).to eq({ "parent" => :success })
    end

    it "does not update `:parent` resource if set in `--resource-versions`" do
      resource_versions = { "parent" => { current_version: "1.2.3", latest_version: version } }
      expect(bump_formula_pr).not_to receive(:update_resource_block!)
      expect(bump_formula_pr.update_matching_version_resources!(f, version:, resource_versions:)).to eq({})
    end
  end

  describe "::update_resources!" do
    let(:f) do
      formula("test") do
        T.bind(self, T.class_of(Formula))
        url "https://brew.sh/test-1.0.0.tgz"

        resource "foo" do
          url "https://brew.sh/foo-1.2.3.tar.gz"
        end
      end
    end
    let(:r) { f.resource("foo") }

    it "updates to requested version" do
      version = "2.1.0"
      resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } }
      expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:success)
      expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :success })
    end

    it "downgrades to requested version" do
      version = "0.1.2"
      resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } }
      expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:success)
      expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :downgraded })
    end

    it "returns update failures" do
      version = "0.1.2"
      resource_versions = { "foo" => { current_version: "1.2.3", latest_version: version } }
      expect(bump_formula_pr).to receive(:update_resource_block!).with(f, r, version).and_return(:url_unchanged)
      expect(bump_formula_pr.update_resources!(f, resource_versions:)).to eq({ "foo" => :url_unchanged })
    end
  end
end
