# frozen_string_literal: true
require "spec_helper"

describe GraphQL::Analysis::QueryComplexity do
  let(:complexities) { [] }
  let(:query_complexity) { GraphQL::Analysis::QueryComplexity.new { |this_query, complexity|  complexities << this_query << complexity } }
  let(:reduce_result) { GraphQL::Analysis.analyze_query(query, [query_complexity]) }
  let(:variables) { {} }
  let(:query) { GraphQL::Query.new(Dummy::Schema.graphql_definition, query_string, variables: variables) }

  describe "simple queries" do
    let(:query_string) {%|
      query cheeses($isSkipped: Boolean = false){
        # complexity of 3
        cheese1: cheese(id: 1) {
          id
          flavor
        }

        # complexity of 4
        cheese2: cheese(id: 2) @skip(if: $isSkipped) {
          similarCheese(source: SHEEP) {
            ... on Cheese {
              similarCheese(source: SHEEP) {
                id
              }
            }
          }
        }
      }
    |}

    it "sums the complexity" do
      reduce_result
      assert_equal complexities, [query, 7]
    end

    describe "when skipped by directives" do
      let(:variables) { { "isSkipped" => true } }
      it "doesn't include skipped fields" do
        reduce_result
        assert_equal complexities, [query, 3]
      end
    end
  end

  describe "query with fragments" do
    let(:query_string) {%|
      {
        # complexity of 3
        cheese1: cheese(id: 1) {
          id
          flavor
        }

        # complexity of 7
        cheese2: cheese(id: 2) {
          ... cheeseFields1
          ... cheeseFields2
        }
      }

      fragment cheeseFields1 on Cheese {
        similarCow: similarCheese(source: COW) {
          id
          ... cheeseFields2
        }
      }

      fragment cheeseFields2 on Cheese {
        similarSheep: similarCheese(source: SHEEP) {
          id
        }
      }
    |}

    it "counts all fragment usages, not the definitions" do
      reduce_result
      assert_equal complexities, [query, 10]
    end

    describe "mutually exclusive types" do
      let(:query_string) {%|
        {
          favoriteEdible {
            # 1 for everybody
            fatContent

            # 1 for everybody
            ... on Edible {
              origin
            }

            # 1 for honey
            ... on Sweetener {
              sweetness
            }

            # 2 for milk
            ... milkFields
            # 1 for cheese
            ... cheeseFields
            # 1 for honey
            ... honeyFields
            # 1 for milk + cheese
            ... dairyProductFields
          }
        }

        fragment milkFields on Milk {
          id
          source
        }

        fragment cheeseFields on Cheese {
          source
        }

        fragment honeyFields on Honey {
          flowerType
        }

        fragment dairyProductFields on DairyProduct {
          ... on Cheese {
            flavor
          }

          ... on Milk {
            flavors
          }
        }
      |}

      it "gets the max among options" do
        reduce_result
        assert_equal 6, complexities.last
      end
    end


    describe "when there are no selections on any object types" do
      let(:query_string) {%|
        {
          # 1 for everybody
          favoriteEdible {
            # 1 for everybody
            fatContent

            # 1 for everybody
            ... on Edible { origin }

            # 1 for honey
            ... on Sweetener { sweetness }
          }
        }
      |}

      it "gets the max among interface types" do
        reduce_result
        assert_equal 4, complexities.last
      end
    end

    describe "redundant fields" do
      let(:query_string) {%|
      {
        favoriteEdible {
          fatContent
          # this is executed separately and counts separately:
          aliasedFatContent: fatContent

          ... on Edible {
            fatContent
          }

          ... edibleFields
        }
      }

      fragment edibleFields on Edible {
        fatContent
      }
      |}

      it "only counts them once" do
        reduce_result
        assert_equal 3, complexities.last
      end
    end
  end

  describe "relay types" do
    let(:query) { GraphQL::Query.new(StarWars::Schema.graphql_definition, query_string) }
    let(:query_string) {%|
    {
      rebels {
        ships {
          edges {
            node {
              id
            }
          }
          pageInfo {
            hasNextPage
          }
        }
      }
    }
    |}

    it "gets the complexity" do
      reduce_result
      assert_equal 7, complexities.last
    end
  end

  describe "custom complexities" do
    let(:query) { GraphQL::Query.new(complexity_schema, query_string) }
    let(:complexity_schema) {
      complexity_interface = GraphQL::InterfaceType.define do
        name "ComplexityInterface"
        field :value, types.Int
      end

      single_complexity_type = GraphQL::ObjectType.define do
        name "SingleComplexity"
        field :value, types.Int, complexity: 0.1 do
          resolve ->(obj, args, ctx) { obj }
        end
        field :complexity, single_complexity_type do
          argument :value, types.Int
          complexity ->(ctx, args, child_complexity) { args[:value] + child_complexity }
          resolve ->(obj, args, ctx) { args[:value] }
        end
        interfaces [complexity_interface]
      end

      double_complexity_type = GraphQL::ObjectType.define do
        name "DoubleComplexity"
        field :value, types.Int, complexity: 4 do
          resolve ->(obj, args, ctx) { obj }
        end
        interfaces [complexity_interface]
      end

      query_type = GraphQL::ObjectType.define do
        name "Query"
        field :complexity, single_complexity_type do
          argument :value, types.Int
          complexity ->(ctx, args, child_complexity) { args[:value] + child_complexity }
          resolve ->(obj, args, ctx) { args[:value] }
        end

        field :innerComplexity, complexity_interface do
          argument :value, types.Int
          resolve ->(obj, args, ctx) { args[:value] }
        end
      end

      GraphQL::Schema.define(
        query: query_type,
        orphan_types: [double_complexity_type],
        resolve_type: ->(a,b,c) { :pass }
      )
    }
    let(:query_string) {%|
      {
        a: complexity(value: 3) { value }
        b: complexity(value: 6) {
          value
          complexity(value: 1) {
            value
          }
        }
      }
    |}

    it "sums the complexity" do
      reduce_result
      # 10 from `complexity`, `0.3` from `value`
      assert_equal complexities, [query, 10.3]
    end

    describe "same field on multiple types" do
      let(:query_string) {%|
      {
        innerComplexity(value: 2) {
          ... on SingleComplexity { value }
          ... on DoubleComplexity { value }
        }
      }
      |}

      it "picks them max for those fields" do
        reduce_result
        # 1 for innerComplexity + 4 for DoubleComplexity.value
        assert_equal complexities, [query, 5]
      end
    end
  end
end
