import {
  GraphQLEnumType,
  Kind,
  DocumentNode,
  validate,
  GraphQLSchema,
  specifiedDirectives,
} from 'graphql';
import { validateSDL } from 'graphql/validation/validate';
import gql from 'graphql-tag';
import { composeServices, buildMapsFromServiceList } from '../../../compose';
import {
  astSerializer,
  typeSerializer,
  selectionSetSerializer,
} from '../../../../snapshotSerializers';
import { normalizeTypeDefs } from '../../../normalize';
import federationDirectives from '../../../../directives';
import { ServiceDefinition } from '../../../types';
import { MatchingEnums } from '../matchingEnums';

expect.addSnapshotSerializer(astSerializer);
expect.addSnapshotSerializer(typeSerializer);
expect.addSnapshotSerializer(selectionSetSerializer);

// simulate the first half of the composition process
const createDefinitionsDocumentForServices = (
  serviceList: ServiceDefinition[],
): DocumentNode => {
  const { typeDefinitionsMap } = buildMapsFromServiceList(serviceList);
  return {
    kind: Kind.DOCUMENT,
    definitions: Object.values(typeDefinitionsMap).flat(),
  };
};

describe('matchingEnums', () => {
  let schema: GraphQLSchema;

  // create a blank schema for each test
  beforeEach(() => {
    schema = new GraphQLSchema({
      query: undefined,
      directives: [...specifiedDirectives, ...federationDirectives],
    });
  });

  it('does not error with matching enums across services', () => {
    const serviceList = [
      {
        typeDefs: gql`
          enum ProductCategory {
            BED
            BATH
          }
        `,
        name: 'serviceA',
      },

      {
        typeDefs: gql`
          enum ProductCategory {
            BED
            BATH
          }
        `,
        name: 'serviceB',
      },
    ];

    const definitionsDocument = createDefinitionsDocumentForServices(
      serviceList,
    );
    const errors = validateSDL(definitionsDocument, schema, [MatchingEnums]);
    expect(errors).toHaveLength(0);
  });

  it('errors when enums in separate services dont match', () => {
    const serviceList = [
      {
        typeDefs: gql`
          enum ProductCategory {
            BED
            BATH
          }
        `,
        name: 'serviceA',
      },
      {
        typeDefs: gql`
          enum ProductCategory {
            BEYOND
          }
        `,
        name: 'serviceB',
      },
    ];

    const definitionsDocument = createDefinitionsDocumentForServices(
      serviceList,
    );
    const errors = validateSDL(definitionsDocument, schema, [MatchingEnums]);
    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: The \`ProductCategory\` enum does not have identical values in all services. Groups of services with identical values are: [serviceA], [serviceB]],
      ]
    `);
  });

  it('errors when enums in separate services dont match', () => {
    const serviceList = [
      {
        typeDefs: gql`
          type Query {
            products: [Product]!
          }

          type Product @key(fields: "sku") {
            sku: String!
            upc: String!
            type: ProductType
          }

          enum ProductType {
            BOOK
            FURNITURE
          }
        `,
        name: 'serviceA',
      },
      {
        typeDefs: gql`
          enum ProductType {
            FURNITURE
            BOOK
            DIGITAL
          }
        `,
        name: 'serviceB',
      },
      {
        typeDefs: gql`
          enum ProductType {
            FURNITURE
            BOOK
            DIGITAL
          }
        `,
        name: 'serviceC',
      },
    ];

    const definitionsDocument = createDefinitionsDocumentForServices(
      serviceList,
    );
    const errors = validateSDL(definitionsDocument, schema, [MatchingEnums]);
    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: The \`ProductType\` enum does not have identical values in all services. Groups of services with identical values are: [serviceA], [serviceB, serviceC]],
      ]
    `);
  });

  it('errors when an enum name is defined as another type in a service', () => {
    const serviceList = [
      {
        typeDefs: gql`
          enum ProductType {
            BOOK
            FURNITURE
          }
        `,
        name: 'serviceA',
      },
      {
        typeDefs: gql`
          type ProductType {
            id: String
          }
        `,
        name: 'serviceB',
      },
      {
        typeDefs: gql`
          enum ProductType {
            FURNITURE
            BOOK
            DIGITAL
          }
        `,
        name: 'serviceC',
      },
    ];

    const definitionsDocument = createDefinitionsDocumentForServices(
      serviceList,
    );
    const errors = validateSDL(definitionsDocument, schema, [MatchingEnums]);
    expect(errors).toMatchInlineSnapshot(`
      Array [
        [GraphQLError: [serviceA] ProductType -> ProductType is an enum in [serviceA, serviceC], but not in [serviceB]],
      ]
    `);
  });
});
