# frozen_string_literal: true

module ValidatorHelpers
  def self.included(child_class)
    child_class.extend(ClassMethods)
  end

  class BlankString < String
    def blank?
      true
    end
  end

  class NonBlankString < String
    if method_defined?(:blank?)
      undef :blank?
    end
  end

  def build_schema(arg_type, validates_config)
    schema = Class.new(GraphQL::Schema)

    base_argument = Class.new(GraphQL::Schema::Argument) do
      def initialize(*args, secret: false, **kwargs, &block)
        super(*args, **kwargs, &block)
        @secret = secret
      end

      def visible?(_ctx)
        !@secret
      end
    end

    base_field = Class.new(GraphQL::Schema::Field) do
      argument_class(base_argument)
    end

    validated_input = Class.new(GraphQL::Schema::InputObject) do
      graphql_name "ValidatedInput"
      argument :a, arg_type, required: false
      argument :b, arg_type, required: false
      argument :c, arg_type, required: false
      validates(validates_config)

      def prepare
        self
      end
    end

    validated_resolver = Class.new(GraphQL::Schema::Resolver) do
      graphql_name "ValidatedResolver"
      argument :a, arg_type, required: false
      argument :b, arg_type, required: false
      argument :c, arg_type, required: false
      validates(validates_config)
      type(arg_type, null: true)
      def resolve(a: 0, b: 0, c: 0)
        a + b + c
      end
    end

    validated_arg_resolver = Class.new(GraphQL::Schema::Resolver) do
      graphql_name "ValidatedArgResolver"
      argument :input, arg_type, required: false, validates: validates_config
      type(String, null: false)
      def resolve(input: :NO_INPUT)
        input.to_s.upcase
      end
    end

    query_type = Class.new(GraphQL::Schema::Object) do
      graphql_name "Query"
      field_class(base_field)
      field :validated, arg_type, resolve_legacy_instance_method: true do
        argument :value, arg_type, required: false, validates: validates_config
      end

      def validated(value: nil)
        value
      end

      field :multi_validated, arg_type, validates: validates_config, resolve_legacy_instance_method: true do
        argument :a, arg_type, required: false
        argument :b, arg_type, required: false
        argument :c, arg_type, required: false
        argument :secret, arg_type, required: false, secret: true
        argument :secret2, arg_type, required: false, secret: true
      end

      def multi_validated(a: 0, b: 0, c: 0)
        a + b + c
      end

      field :validated_input, arg_type, resolve_legacy_instance_method: true do
        argument :input, validated_input
      end

      def validated_input(input:)
        (input[:a] || 0) + (input[:b] || 0) + (input[:c] || 0)
      end

      field :validated_resolver, resolver: validated_resolver
      field :validated_arg_resolver, resolver: validated_arg_resolver
      field :list, [self], null: false, resolve_legacy_instance_method: true

      def list
        [:a, :b, :c]
      end
    end

    schema.query(query_type)
    if ADD_WARDEN
      schema.use(GraphQL::Schema::Warden)
    else
      schema.use(GraphQL::Schema::Visibility)
    end

    @current_schema = schema
  end

  def exec_query(...)
    @current_schema.execute(...)
  end

  module ClassMethods
    def build_tests(validator_name, field_type, expectations)
      expectations.each do |expectation|
        name = expectation[:name] ? "#{expectation[:name]}: " : ""
        it "#{name}#{validator_name} on #{field_type} works with #{expectation[:config]}" do
          schema = build_schema(field_type, { validator_name => expectation[:config] })
          expectation[:cases].each do |test_case|
            result = schema.execute(test_case[:query], variables: test_case[:variables])
            if !result["data"]
              pp result
              refute result["errors"].map { |e| e["message"] }, test_case[:query]
            end

            assert_equal test_case[:error_messages], (result["errors"] || []).map { |e| e["message"] }, test_case[:query]

            if test_case[:result].nil?
              assert_nil result["data"]["validated"], test_case[:query]
            else
              assert_equal test_case[:result], result["data"]["validated"], test_case[:query]
            end
          end
        end
      end
    end
  end
end
