# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'getting epics information' do
  include GraphqlHelpers

  let_it_be(:user) { create(:user) }
  let_it_be(:group) { create(:group) }

  before do
    group.add_maintainer(user)
    stub_licensed_features(epics: true)
  end

  describe 'query for epics which start with an iid' do
    let_it_be(:epic1) { create(:epic, group: group, iid: 11) }
    let_it_be(:epic2) { create(:epic, group: group, iid: 22) }
    let_it_be(:epic3) { create(:epic, group: group, iid: 2223) }
    let_it_be(:epic4) { create(:epic, group: group, iid: 29) }

    context 'when a valid iidStartsWith query is provided' do
      it 'returns the expected epics' do
        query_epics_which_start_with_iid('22')

        expect_epics_response(epic2, epic3)
      end
    end

    context 'when invalid iidStartsWith query is provided' do
      it 'fails with negative number' do
        query_epics_which_start_with_iid('-2')

        expect(graphql_errors).to include(a_hash_including('message' => 'Invalid `iidStartsWith` query'))
      end

      it 'fails with string' do
        query_epics_which_start_with_iid('foo')

        expect(graphql_errors).to include(a_hash_including('message' => 'Invalid `iidStartsWith` query'))
      end

      it 'fails if query contains line breaks' do
        query_epics_which_start_with_iid('2\nfoo')

        expect(graphql_errors).to include(a_hash_including('message' => 'Invalid `iidStartsWith` query'))
      end
    end

    def query_epics_which_start_with_iid(iid)
      post_graphql(epics_query(group, 'iidStartsWith', iid), current_user: user)
    end
  end

  describe 'query for epics by time frame' do
    let_it_be(:epic1) { create(:epic, group: group, state: :opened, start_date: "2019-08-13", end_date: "2019-08-20") }
    let_it_be(:epic2) { create(:epic, group: group, state: :closed, start_date: "2019-08-13", end_date: "2019-08-21") }
    let_it_be(:epic3) { create(:epic, group: group, state: :closed, start_date: "2019-08-22", end_date: "2019-08-26") }
    let_it_be(:epic4) { create(:epic, group: group, state: :closed, start_date: "2019-08-10", end_date: "2019-08-12") }

    context 'when start_date and end_date are present' do
      it 'returns epics within timeframe' do
        post_graphql(epics_query_by_hash(group, 'startDate' => '2019-08-13', 'endDate' => '2019-08-21'), current_user: user)

        expect_epics_response(epic1, epic2)
      end
    end

    context 'when only start_date is present' do
      it 'raises error' do
        post_graphql(epics_query(group, 'startDate', '2019-08-13'), current_user: user)

        expect(graphql_errors).to include(a_hash_including('message' => 'Both startDate and endDate must be present.'))
      end
    end

    context 'when only end_date is present' do
      it 'raises error' do
        post_graphql(epics_query(group, 'endDate', '2019-08-13'), current_user: user)

        expect(graphql_errors).to include(a_hash_including('message' => 'Both startDate and endDate must be present.'))
      end
    end
  end

  context 'query for epics with events' do
    let_it_be(:epic) { create(:epic, group: group) }

    it 'can lookahead to prevent N+1 queries' do
      create_list(:event, 10, :created, target: epic, group: group)

      control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
        query_epics_with_events(1)
      end.count

      events = graphql_dig_at(graphql_data, :group, :epics, :nodes, :events, :nodes)
      expect(events.count).to eq(1)

      expect do
        query_epics_with_events(10)
      end.not_to exceed_all_query_limit(control_count)

      data = graphql_data(fresh_response_data)
      events = graphql_dig_at(data, :group, :epics, :nodes, :events, :nodes)
      expect(events.count).to eq(10)
    end
  end

  context 'query for epics with ancestors' do
    let_it_be(:parent_epic) { create(:epic, group: group) }
    let_it_be(:epic) { create(:epic, group: group, parent: parent_epic) }

    it 'returns the ancestors' do
      query_epic_with_ancestors(epic.iid)

      ancestors = graphql_data['group']['epic']['ancestors']['nodes']

      expect(ancestors.count).to eq(1)
      expect(ancestors.first['id']).to eq(parent_epic.to_global_id.to_s)
      expect(graphql_errors).to be_nil
    end
  end

  describe 'N+1 query checks' do
    let_it_be(:epic_a) { create(:epic, group: group) }
    let_it_be(:epic_b) { create(:epic, group: group) }

    let(:extra_iid_for_second_query) { epic_b.iid.to_s }
    let(:search_params) { { iids: [epic_a.iid.to_s] } }

    def execute_query
      query = graphql_query_for(
        :group,
        { full_path: group.full_path },
        query_graphql_field(
          :epics, search_params,
          query_graphql_field(:nodes, nil, requested_fields)
        )
      )
      post_graphql(query, current_user: user)
    end

    context 'when requesting `award_emoji`' do
      let(:requested_fields) { 'awardEmoji { nodes { name } }' }

      before do
        create(:award_emoji, awardable: epic_a, user: user)
        create(:award_emoji, awardable: epic_b, user: user)
      end

      include_examples 'N+1 query check'
    end
  end

  def query_epics_with_events(number)
    epics_field = <<~NODE
      epics {
        nodes {
          id
          events(first: #{number}) {
            nodes {
              id
            }
          }
        }
      }
    NODE

    post_graphql(
      graphql_query_for('group', { 'fullPath' => group.full_path }, epics_field),
      current_user: user
    )
  end

  def query_epic_with_ancestors(epic_iid)
    epics_field = <<~NODE
      epic(iid: #{epic_iid}) {
        id
        ancestors {
          nodes {
            id
          }
        }
      }
    NODE

    post_graphql(
      graphql_query_for('group', { 'fullPath' => group.full_path }, epics_field),
      current_user: user
    )
  end

  def epics_query(group, field, value)
    epics_query_by_hash(group, field => value)
  end

  def epics_query_by_hash(group, args = {})
    field_queries = args.map { |key, value| "#{key}:\"#{value}\"" }.join(',')
    field_queries = " ( #{field_queries} ) " if field_queries.present?

    <<~QUERY
      query {
        group(fullPath: "#{group.full_path}") {
          id,
          epics#{field_queries} {
            nodes {
              id
            }
          }
        }
      }
    QUERY
  end

  def expect_epics_response(*epics)
    actual_epics = graphql_data['group']['epics']['nodes'].map { |epic| epic['id'] }
    expected_epics = epics.map { |epic| epic.to_global_id.to_s }

    expect(actual_epics).to contain_exactly(*expected_epics)
    expect(graphql_errors).to be_nil
  end
end