# frozen_string_literal: true

require_relative './encoded_api_version_data'
require_relative './input_env'
require_relative './input_is_invalid'
require_relative './match_header_against_api_versions'
require_relative './report_no_matching_version'

# All(?) Rack code is namespaced within this module.
module Rack
  # Module includes our middleware components for managing service API versions.
  module ServiceApiVersioning
    # Select API Version of Component Service based on HTTP `Accept` header.
    class AcceptContentTypeSelector
      def initialize(app)
        @app = app
        @env = nil
        @input = nil
        self
      end

      def call(env)
        @env = env
        @input = InputEnv.new env
        match_request_from_header
      end

      private

      attr_reader :app, :env, :input

      def add_component_api_version_data(version)
        env['COMPONENT_API_VERSION_DATA'] = encoded_data_for(version)
        self
      end

      def api_versions
        input.data[:api_versions]
      end

      def encoded_data_for(api_version)
        EncodedApiVersionData.call(api_version: api_version, data: input.data)
      end

      def if_component_apis_defined(&_)
        InputIsInvalid.call(input) || yield
      end

      def if_requested_api_version_found
        best_version = specified_api_version
        return report_no_matching_api_versions unless best_version
        yield best_version
      end

      def match_request_from_header
        if_component_apis_defined do
          if_requested_api_version_found do |matching_version|
            use_api_version(matching_version)
          end
        end
      end

      def report_no_matching_api_versions
        ReportNoMatchingVersion.call api_versions: api_versions
      end

      def specified_api_version
        MatchHeaderAgainstApiVersions.call(accept_header: env['HTTP_ACCEPT'],
                                           api_versions: api_versions)
      end

      def use_api_version(matching_version)
        add_component_api_version_data(matching_version)
        app.call env
      end
    end # class AcceptContentTypeSelector
  end
end