# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

require File.expand_path("../../../../base", __FILE__)

describe Vagrant::Action::Builtin::Call do
  let(:app) { lambda { |env| } }
  let(:env) { {} }

  def wrapper_proc(data)
    Class.new do
      def initialize(app, env)
        @app = app
      end

      def self.name
        "TestAction"
      end

      define_method(:call) do |env|
        env[:data] << "#{data}_in"
        @app.call(env)
        env[:data] << "#{data}_out"
      end
    end
  end

  it "should yield the env to the block" do
    received = nil

    callable = lambda do |env|
      env[:result] = "value"
    end

    described_class.new(app, env, callable) do |env, builder|
      received = env[:result]
    end.call({})

    expect(received).to eq("value")
  end

  it "should update the original env with any changes" do
    callable = lambda { |env| }
    next_step = lambda { |env| env[:inner] = true }

    described_class.new(app, env, callable) do |_env, builder|
      builder.use next_step
    end.call(env)

    expect(env[:inner]).to eq(true)
  end

  it "should call the callable with the original environment" do
    received = nil
    callable = lambda { |env| received = env[:foo] }

    described_class.new(app, env, callable) do |_env, _builder|
      # Nothing.
    end.call({ foo: :bar })

    expect(received).to eq(:bar)
   end

  it "should call the next builder" do
    received = nil
    callable = lambda { |env| }
    next_step = lambda { |env| received = "value" }

    described_class.new(app, env, callable) do |_env, builder|
      builder.use next_step
    end.call({})

    expect(received).to eq("value")
  end

  it "should call the next builder with the original environment" do
    received = nil
    callable = lambda { |env| }
    next_step = lambda { |env| received = env[:foo] }

    described_class.new(app, env, callable) do |_env, builder|
      builder.use next_step
    end.call({ foo: :bar })

    expect(received).to eq(:bar)
  end

  it "should call the next builder inserted in our own stack" do
    callable = lambda { |env| }

    builder = Vagrant::Action::Builder.new.tap do |b|
      b.use wrapper_proc(1)
      b.use described_class, callable do |_env, b2|
        b2.use wrapper_proc(2)
      end
      b.use wrapper_proc(3)
    end

    env = { data: [] }
    builder.call(env)
    expect(env[:data]).to eq([
      "1_in", "2_in", "3_in", "3_out", "2_out", "1_out"])
  end

  it "should instantiate the callable with the extra args" do
    env = {}

    callable = Class.new do
      def initialize(app, env, arg)
        env[:arg] = arg
      end

      def self.name
        "TestAction"
      end

      def call(env); end
    end

    result = nil
    instance = described_class.new(app, env, callable, :foo) do |inner_env, _builder|
      result = inner_env[:arg]
    end
    instance.call(env)

    expect(result).to eq(:foo)
  end

  it "should call the recover method for the sequence in an error" do
    # Basic variables
    callable = lambda { |env| }

    # Build the steps for the test
    basic_step = Class.new do
      def initialize(app, env)
        @app = app
        @env = env
      end

      def self.name
        "TestAction"
      end

      def call(env)
        @app.call(env)
      end
    end

    step_a = Class.new(basic_step) do
      def call(env)
        env[:steps] << :call_A
        super
      end

      def self.name
        "TestAction"
      end

      def recover(env)
        env[:steps] << :recover_A
      end
    end

    step_b = Class.new(basic_step) do
      def call(env)
        env[:steps] << :call_B
        super
      end

      def self.name
        "TestAction"
      end

      def recover(env)
        env[:steps] << :recover_B
      end
    end

    instance = described_class.new(app, env, callable) do |_env, builder|
      builder.use step_a
      builder.use step_b
    end

    env[:steps] = []
    instance.call(env)
    instance.recover(env)

    expect(env[:steps]).to eq([:call_A, :call_B, :recover_B, :recover_A])
  end

  it "should recover even if it failed in the callable" do
    callable = lambda { |env| raise "error" }

    instance = described_class.new(app, env, callable) { |_env, _builder| }
    instance.call(env) rescue nil
    expect { instance.recover(env) }.
      to_not raise_error
  end
end
