# frozen_string_literal: true

require_relative 'base_matcher'
require_relative './have_a_field_matchers/of_type'
require_relative './have_a_field_matchers/with_property'
require_relative './have_a_field_matchers/with_metadata'
require_relative './have_a_field_matchers/with_hash_key'
require_relative './have_a_field_matchers/with_deprecation_reason'

module RSpec
  module GraphqlMatchers
    class HaveAField < BaseMatcher
      def initialize(expected_field_name, fields = :fields)
        @expected_field_name = expected_field_name.to_s
        @expected_camel_field_name = GraphQL::Schema::Member::BuildType.camelize(
          @expected_field_name
        )
        @fields = fields.to_sym
        @expectations = []
      end

      def matches?(graph_object)
        @graph_object = graph_object

        return false if actual_field.nil?

        @results = @expectations.reject do |matcher|
          matcher.matches?(actual_field)
        end

        @results.empty?
      end

      def that_returns(expected_field_type)
        @expectations << HaveAFieldMatchers::OfType.new(expected_field_type)
        self
      end

      alias returning that_returns
      alias of_type that_returns

      def with_property(expected_property_name)
        @expectations << HaveAFieldMatchers::WithProperty.new(expected_property_name)
        self
      end

      def with_hash_key(expected_hash_key)
        @expectations << HaveAFieldMatchers::WithHashKey.new(expected_hash_key)

        self
      end

      def with_metadata(expected_metadata)
        @expectations << HaveAFieldMatchers::WithMetadata.new(expected_metadata)
        self
      end

      def with_deprecation_reason(expected_reason = nil)
        @expectations << HaveAFieldMatchers::WithDeprecationReason.new(expected_reason)
        self
      end

      def failure_message
        base_msg = "expected #{member_name(@graph_object)} " \
          "to define field `#{@expected_field_name}`" \

        return "#{base_msg} #{failure_messages.join(', ')}" if actual_field

        "#{base_msg} but no field was found with that name"
      end

      def description
        ["define field `#{@expected_field_name}`"].concat(descriptions).join(', ')
      end

      private

      def actual_field
        @actual_field ||= begin
          field = field_collection[@expected_field_name]
          field ||= field_collection[@expected_camel_field_name]

          field.respond_to?(:to_type_signature) ? field.to_type_signature : field
        end
      end

      def descriptions
        results.map(&:description)
      end

      def failure_messages
        results.map(&:failure_message)
      end

      def field_collection
        if @graph_object.respond_to?(@fields)
          @graph_object.public_send(@fields)
        else
          raise "Invalid object #{@graph_object} provided to #{matcher_name} " \
            'matcher. It does not seem to be a valid GraphQL object type.'
        end
      end

      def matcher_name
        case @fields
        when :fields then 'have_a_field'
        when :arguments then 'have_an_input_field'
        end
      end

      def results
        @results ||= []
      end
    end
  end
end
