import { describe, it } from 'mocha';

import type { GraphQLSchema } from '../../type/schema.js';

import { buildSchema } from '../../utilities/buildASTSchema.js';

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

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

function expectSDLErrors(sdlStr: string, schema?: GraphQLSchema) {
  return expectSDLValidationErrors(
    schema,
    UniqueFieldDefinitionNamesRule,
    sdlStr,
  );
}

function expectValidSDL(sdlStr: string, schema?: GraphQLSchema) {
  expectSDLErrors(sdlStr, schema).toDeepEqual([]);
}

describe('Validate: Unique field definition names', () => {
  it('no fields', () => {
    expectValidSDL(`
      type SomeObject
      interface SomeInterface
      input SomeInputObject
    `);
  });

  it('one field', () => {
    expectValidSDL(`
      type SomeObject {
        foo: String
      }

      interface SomeInterface {
        foo: String
      }

      input SomeInputObject {
        foo: String
      }
    `);
  });

  it('multiple fields', () => {
    expectValidSDL(`
      type SomeObject {
        foo: String
        bar: String
      }

      interface SomeInterface {
        foo: String
        bar: String
      }

      input SomeInputObject {
        foo: String
        bar: String
      }
    `);
  });

  it('duplicate fields inside the same type definition', () => {
    expectSDLErrors(`
      type SomeObject {
        foo: String
        bar: String
        foo: String
      }

      interface SomeInterface {
        foo: String
        bar: String
        foo: String
      }

      input SomeInputObject {
        foo: String
        bar: String
        foo: String
      }
    `).toDeepEqual([
      {
        message: 'Field "SomeObject.foo" can only be defined once.',
        locations: [
          { line: 3, column: 9 },
          { line: 5, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInterface.foo" can only be defined once.',
        locations: [
          { line: 9, column: 9 },
          { line: 11, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInputObject.foo" can only be defined once.',
        locations: [
          { line: 15, column: 9 },
          { line: 17, column: 9 },
        ],
      },
    ]);
  });

  it('extend type with new field', () => {
    expectValidSDL(`
      type SomeObject {
        foo: String
      }
      extend type SomeObject {
        bar: String
      }
      extend type SomeObject {
        baz: String
      }

      interface SomeInterface {
        foo: String
      }
      extend interface SomeInterface {
        bar: String
      }
      extend interface SomeInterface {
        baz: String
      }

      input SomeInputObject {
        foo: String
      }
      extend input SomeInputObject {
        bar: String
      }
      extend input SomeInputObject {
        baz: String
      }
    `);
  });

  it('extend type with duplicate field', () => {
    expectSDLErrors(`
      extend type SomeObject {
        foo: String
      }
      type SomeObject {
        foo: String
      }

      extend interface SomeInterface {
        foo: String
      }
      interface SomeInterface {
        foo: String
      }

      extend input SomeInputObject {
        foo: String
      }
      input SomeInputObject {
        foo: String
      }
    `).toDeepEqual([
      {
        message: 'Field "SomeObject.foo" can only be defined once.',
        locations: [
          { line: 3, column: 9 },
          { line: 6, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInterface.foo" can only be defined once.',
        locations: [
          { line: 10, column: 9 },
          { line: 13, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInputObject.foo" can only be defined once.',
        locations: [
          { line: 17, column: 9 },
          { line: 20, column: 9 },
        ],
      },
    ]);
  });

  it('duplicate field inside extension', () => {
    expectSDLErrors(`
      type SomeObject
      extend type SomeObject {
        foo: String
        bar: String
        foo: String
      }

      interface SomeInterface
      extend interface SomeInterface {
        foo: String
        bar: String
        foo: String
      }

      input SomeInputObject
      extend input SomeInputObject {
        foo: String
        bar: String
        foo: String
      }
    `).toDeepEqual([
      {
        message: 'Field "SomeObject.foo" can only be defined once.',
        locations: [
          { line: 4, column: 9 },
          { line: 6, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInterface.foo" can only be defined once.',
        locations: [
          { line: 11, column: 9 },
          { line: 13, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInputObject.foo" can only be defined once.',
        locations: [
          { line: 18, column: 9 },
          { line: 20, column: 9 },
        ],
      },
    ]);
  });

  it('duplicate field inside different extensions', () => {
    expectSDLErrors(`
      type SomeObject
      extend type SomeObject {
        foo: String
      }
      extend type SomeObject {
        foo: String
      }

      interface SomeInterface
      extend interface SomeInterface {
        foo: String
      }
      extend interface SomeInterface {
        foo: String
      }

      input SomeInputObject
      extend input SomeInputObject {
        foo: String
      }
      extend input SomeInputObject {
        foo: String
      }
    `).toDeepEqual([
      {
        message: 'Field "SomeObject.foo" can only be defined once.',
        locations: [
          { line: 4, column: 9 },
          { line: 7, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInterface.foo" can only be defined once.',
        locations: [
          { line: 12, column: 9 },
          { line: 15, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInputObject.foo" can only be defined once.',
        locations: [
          { line: 20, column: 9 },
          { line: 23, column: 9 },
        ],
      },
    ]);
  });

  it('adding new field to the type inside existing schema', () => {
    const schema = buildSchema(`
      type SomeObject
      interface SomeInterface
      input SomeInputObject
    `);
    const sdl = `
      extend type SomeObject {
        foo: String
      }

      extend interface SomeInterface {
        foo: String
      }

      extend input SomeInputObject {
        foo: String
      }
    `;

    expectValidSDL(sdl, schema);
  });

  it('adding conflicting fields to existing schema twice', () => {
    const schema = buildSchema(`
      type SomeObject {
        foo: String
      }

      interface SomeInterface {
        foo: String
      }

      input SomeInputObject {
        foo: String
      }
    `);
    const sdl = `
      extend type SomeObject {
        foo: String
      }
      extend interface SomeInterface {
        foo: String
      }
      extend input SomeInputObject {
        foo: String
      }

      extend type SomeObject {
        foo: String
      }
      extend interface SomeInterface {
        foo: String
      }
      extend input SomeInputObject {
        foo: String
      }
    `;

    expectSDLErrors(sdl, schema).toDeepEqual([
      {
        message:
          'Field "SomeObject.foo" already exists in the schema. It cannot also be defined in this type extension.',
        locations: [{ line: 3, column: 9 }],
      },
      {
        message:
          'Field "SomeInterface.foo" already exists in the schema. It cannot also be defined in this type extension.',
        locations: [{ line: 6, column: 9 }],
      },
      {
        message:
          'Field "SomeInputObject.foo" already exists in the schema. It cannot also be defined in this type extension.',
        locations: [{ line: 9, column: 9 }],
      },
      {
        message:
          'Field "SomeObject.foo" already exists in the schema. It cannot also be defined in this type extension.',
        locations: [{ line: 13, column: 9 }],
      },
      {
        message:
          'Field "SomeInterface.foo" already exists in the schema. It cannot also be defined in this type extension.',
        locations: [{ line: 16, column: 9 }],
      },
      {
        message:
          'Field "SomeInputObject.foo" already exists in the schema. It cannot also be defined in this type extension.',
        locations: [{ line: 19, column: 9 }],
      },
    ]);
  });

  it('adding fields to existing schema twice', () => {
    const schema = buildSchema(`
      type SomeObject
      interface SomeInterface
      input SomeInputObject
    `);
    const sdl = `
      extend type SomeObject {
        foo: String
      }
      extend type SomeObject {
        foo: String
      }

      extend interface SomeInterface {
        foo: String
      }
      extend interface SomeInterface {
        foo: String
      }

      extend input SomeInputObject {
        foo: String
      }
      extend input SomeInputObject {
        foo: String
      }
    `;

    expectSDLErrors(sdl, schema).toDeepEqual([
      {
        message: 'Field "SomeObject.foo" can only be defined once.',
        locations: [
          { line: 3, column: 9 },
          { line: 6, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInterface.foo" can only be defined once.',
        locations: [
          { line: 10, column: 9 },
          { line: 13, column: 9 },
        ],
      },
      {
        message: 'Field "SomeInputObject.foo" can only be defined once.',
        locations: [
          { line: 17, column: 9 },
          { line: 20, column: 9 },
        ],
      },
    ]);
  });
});