# frozen_string_literal: true

require 'ruby_terraform'

module RSpec
  module Terraform
    module Helpers
      class Output
        attr_reader(
          :configuration_provider, :binary, :execution_mode
        )

        def initialize(opts = {})
          @configuration_provider =
            opts[:configuration_provider] || Configuration.identity_provider
          @binary = RSpec.configuration.terraform_binary
          @execution_mode = RSpec.configuration.terraform_execution_mode
        end

        def execute(overrides = {})
          parameters = with_configuration_provider_parameters(overrides)
          parameters = with_mandatory_parameters(parameters)

          ensure_required_parameters(parameters)

          clean(parameters)
          init(parameters)
          output_value = output(parameters)

          JSON.parse(output_value, symbolize_names: true)
        end

        private

        def with_configuration_provider_parameters(parameters)
          configuration_provider.resolve(parameters)
        end

        def with_mandatory_parameters(parameters)
          parameters.merge(
            json: true
          )
        end

        def required_parameters(execution_mode)
          {
            in_place: %i[name configuration_directory],
            isolated: %i[name source_directory configuration_directory]
          }[execution_mode] || []
        end

        def ensure_required_parameters(parameters)
          missing_parameters =
            required_parameters(execution_mode)
              .filter { |parameter| parameters[parameter].nil? }

          return if missing_parameters.empty?

          raise_missing_parameters(missing_parameters)
        end

        def raise_missing_parameters(parameters)
          parameters = parameters.collect { |parameter| "`:#{parameter}`" }
          if parameters.count == 1
            raise StandardError,
                  "Required parameter: #{parameters[0]} missing."
          else
            parameters = "#{parameters[..-2].join(', ')} and #{parameters[-1]}"
            raise StandardError,
                  "Required parameters: #{parameters} missing."
          end
        end

        def clean(parameters)
          return unless execution_mode == :isolated

          FileUtils.rm_rf(parameters[:configuration_directory])
          FileUtils.mkdir_p(parameters[:configuration_directory])
        end

        def init(parameters)
          init_command.execute(init_parameters(parameters))
        end

        def output(parameters)
          stdout = StringIO.new
          output_command(stdout: stdout)
            .execute(output_parameters(parameters))
          stdout.string
        end

        def init_command
          RubyTerraform::Commands::Init.new(binary: binary)
        end

        def output_command(opts = {})
          RubyTerraform::Commands::Output.new(opts.merge(binary: binary))
        end

        def init_parameters(parameters)
          init_parameters = parameters.merge(
            chdir: parameters[:configuration_directory],
            input: false
          )
          if execution_mode == :isolated
            init_parameters =
              init_parameters.merge(from_module: parameters[:source_directory])
          end

          init_parameters
        end

        def output_parameters(parameters)
          output_parameters =
            parameters.merge(
              chdir: parameters[:configuration_directory]
            )

          if parameters[:state_file]
            output_parameters =
              output_parameters.merge(state: parameters[:state_file])
          end

          output_parameters
        end
      end
    end
  end
end