import { describe, it } from 'mocha';

import { NoFragmentCyclesRule } from '../rules/NoFragmentCyclesRule.js';

import { expectValidationErrors } from './harness.js';

function expectErrors(queryStr: string) {
  return expectValidationErrors(NoFragmentCyclesRule, queryStr);
}

function expectValid(queryStr: string) {
  expectErrors(queryStr).toDeepEqual([]);
}

describe('Validate: No circular fragment spreads', () => {
  it('single reference is valid', () => {
    expectValid(`
      fragment fragA on Dog { ...fragB }
      fragment fragB on Dog { name }
    `);
  });

  it('spreading twice is not circular', () => {
    expectValid(`
      fragment fragA on Dog { ...fragB, ...fragB }
      fragment fragB on Dog { name }
    `);
  });

  it('spreading twice indirectly is not circular', () => {
    expectValid(`
      fragment fragA on Dog { ...fragB, ...fragC }
      fragment fragB on Dog { ...fragC }
      fragment fragC on Dog { name }
    `);
  });

  it('double spread within abstract types', () => {
    expectValid(`
      fragment nameFragment on Pet {
        ... on Dog { name }
        ... on Cat { name }
      }

      fragment spreadsInAnon on Pet {
        ... on Dog { ...nameFragment }
        ... on Cat { ...nameFragment }
      }
    `);
  });

  it('does not false positive on unknown fragment', () => {
    expectValid(`
      fragment nameFragment on Pet {
        ...UnknownFragment
      }
    `);
  });

  it('spreading recursively within field fails', () => {
    expectErrors(`
      fragment fragA on Human { relatives { ...fragA } },
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragA" within itself.',
        locations: [{ line: 2, column: 45 }],
      },
    ]);
  });

  it('no spreading itself directly', () => {
    expectErrors(`
      fragment fragA on Dog { ...fragA }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragA" within itself.',
        locations: [{ line: 2, column: 31 }],
      },
    ]);
  });

  it('no spreading itself directly within inline fragment', () => {
    expectErrors(`
      fragment fragA on Pet {
        ... on Dog {
          ...fragA
        }
      }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragA" within itself.',
        locations: [{ line: 4, column: 11 }],
      },
    ]);
  });

  it('no spreading itself indirectly', () => {
    expectErrors(`
      fragment fragA on Dog { ...fragB }
      fragment fragB on Dog { ...fragA }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragA" within itself via "fragB".',
        locations: [
          { line: 2, column: 31 },
          { line: 3, column: 31 },
        ],
      },
    ]);
  });

  it('no spreading itself indirectly reports opposite order', () => {
    expectErrors(`
      fragment fragB on Dog { ...fragA }
      fragment fragA on Dog { ...fragB }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragB" within itself via "fragA".',
        locations: [
          { line: 2, column: 31 },
          { line: 3, column: 31 },
        ],
      },
    ]);
  });

  it('no spreading itself indirectly within inline fragment', () => {
    expectErrors(`
      fragment fragA on Pet {
        ... on Dog {
          ...fragB
        }
      }
      fragment fragB on Pet {
        ... on Dog {
          ...fragA
        }
      }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragA" within itself via "fragB".',
        locations: [
          { line: 4, column: 11 },
          { line: 9, column: 11 },
        ],
      },
    ]);
  });

  it('no spreading itself deeply', () => {
    expectErrors(`
      fragment fragA on Dog { ...fragB }
      fragment fragB on Dog { ...fragC }
      fragment fragC on Dog { ...fragO }
      fragment fragX on Dog { ...fragY }
      fragment fragY on Dog { ...fragZ }
      fragment fragZ on Dog { ...fragO }
      fragment fragO on Dog { ...fragP }
      fragment fragP on Dog { ...fragA, ...fragX }
    `).toDeepEqual([
      {
        message:
          'Cannot spread fragment "fragA" within itself via "fragB", "fragC", "fragO", "fragP".',
        locations: [
          { line: 2, column: 31 },
          { line: 3, column: 31 },
          { line: 4, column: 31 },
          { line: 8, column: 31 },
          { line: 9, column: 31 },
        ],
      },
      {
        message:
          'Cannot spread fragment "fragO" within itself via "fragP", "fragX", "fragY", "fragZ".',
        locations: [
          { line: 8, column: 31 },
          { line: 9, column: 41 },
          { line: 5, column: 31 },
          { line: 6, column: 31 },
          { line: 7, column: 31 },
        ],
      },
    ]);
  });

  it('no spreading itself deeply two paths', () => {
    expectErrors(`
      fragment fragA on Dog { ...fragB, ...fragC }
      fragment fragB on Dog { ...fragA }
      fragment fragC on Dog { ...fragA }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragA" within itself via "fragB".',
        locations: [
          { line: 2, column: 31 },
          { line: 3, column: 31 },
        ],
      },
      {
        message: 'Cannot spread fragment "fragA" within itself via "fragC".',
        locations: [
          { line: 2, column: 41 },
          { line: 4, column: 31 },
        ],
      },
    ]);
  });

  it('no spreading itself deeply two paths -- alt traverse order', () => {
    expectErrors(`
      fragment fragA on Dog { ...fragC }
      fragment fragB on Dog { ...fragC }
      fragment fragC on Dog { ...fragA, ...fragB }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragA" within itself via "fragC".',
        locations: [
          { line: 2, column: 31 },
          { line: 4, column: 31 },
        ],
      },
      {
        message: 'Cannot spread fragment "fragC" within itself via "fragB".',
        locations: [
          { line: 4, column: 41 },
          { line: 3, column: 31 },
        ],
      },
    ]);
  });

  it('no spreading itself deeply and immediately', () => {
    expectErrors(`
      fragment fragA on Dog { ...fragB }
      fragment fragB on Dog { ...fragB, ...fragC }
      fragment fragC on Dog { ...fragA, ...fragB }
    `).toDeepEqual([
      {
        message: 'Cannot spread fragment "fragB" within itself.',
        locations: [{ line: 3, column: 31 }],
      },
      {
        message:
          'Cannot spread fragment "fragA" within itself via "fragB", "fragC".',
        locations: [
          { line: 2, column: 31 },
          { line: 3, column: 41 },
          { line: 4, column: 31 },
        ],
      },
      {
        message: 'Cannot spread fragment "fragB" within itself via "fragC".',
        locations: [
          { line: 3, column: 41 },
          { line: 4, column: 41 },
        ],
      },
    ]);
  });
});