import gql from 'graphql-tag';
import { execute, ServiceDefinitionModule } from '../execution-utils';
import { astSerializer, queryPlanSerializer } from '../../snapshotSerializers';

expect.addSnapshotSerializer(astSerializer);
expect.addSnapshotSerializer(queryPlanSerializer);

const users = [
  { id: '1', name: 'Trevor Scheer', organizationId: '1', __typename: 'User' },
  { id: '1', name: 'Trevor Scheer', organizationId: '2', __typename: 'User' },
  { id: '2', name: 'James Baxley', organizationId: '1', __typename: 'User' },
  { id: '2', name: 'James Baxley', organizationId: '3', __typename: 'User' },
];

const organizations = [
  { id: '1', name: 'Apollo', __typename: 'Organization' },
  { id: '2', name: 'Wayfair', __typename: 'Organization' },
  { id: '3', name: 'Major League Soccer', __typename: 'Organization' },
];

const reviews = [
  { id: '1', authorId: '1', organizationId: '1', __typename: 'Review' },
  { id: '2', authorId: '1', organizationId: '2', __typename: 'Review' },
  { id: '3', authorId: '2', organizationId: '1', __typename: 'Review' },
  { id: '4', authorId: '2', organizationId: '3', __typename: 'Review' },
];

const reviewService: ServiceDefinitionModule = {
  name: 'review',
  typeDefs: gql`
    type Query {
      reviews: [Review!]!
    }

    type Review {
      id: ID!
      author: User!
      body: String!
    }

    # TODO: consider ergonomics of external types.
    extend type User @key(fields: "id organization { id }") {
      id: ID! @external
      organization: Organization! @external
    }

    extend type Organization {
      id: ID! @external
    }
  `,
  resolvers: {
    Query: {
      reviews() {
        return reviews;
      },
    },
    Review: {
      author(review) {
        return {
          id: review.authorId,
          organization: {
            id: review.organizationId,
          },
        };
      },
    },
  },
};

const userService: ServiceDefinitionModule = {
  name: 'user',
  typeDefs: gql`
    type User @key(fields: "id organization { id }") {
      id: ID!
      name: String!
      organization: Organization!
    }

    type Organization @key(fields: "id") {
      id: ID!
      name: String!
    }
  `,
  resolvers: {
    User: {
      __resolveReference(reference) {
        return users.find(
          user =>
            user.id === reference.id &&
            user.organizationId === reference.organization.id,
        );
      },
      organization(user) {
        return { id: user.organizationId };
      },
    },
    Organization: {
      __resolveObject(object) {
        return organizations.find(org => org.id === object.id);
      },
    },
  },
};

it('works fetches data correctly with complex / nested @key fields', async () => {
  const query = `#graphql
    query Reviews {
      reviews {
        author {
          name
          organization {
            name
          }
        }
      }
    }
  `;

  const { data, queryPlan } = await execute(
    {
      query,
    },
    [userService, reviewService],
  );

  expect(data).toEqual({
    reviews: [
      {
        author: {
          name: 'Trevor Scheer',
          organization: {
            name: 'Apollo',
          },
        },
      },
      {
        author: {
          name: 'Trevor Scheer',
          organization: {
            name: 'Wayfair',
          },
        },
      },
      {
        author: {
          name: 'James Baxley',
          organization: {
            name: 'Apollo',
          },
        },
      },
      {
        author: {
          name: 'James Baxley',
          organization: {
            name: 'Major League Soccer',
          },
        },
      },
    ],
  });
  expect(queryPlan).toMatchInlineSnapshot(`
    QueryPlan {
      Sequence {
        Fetch(service: "review") {
          {
            reviews {
              author {
                __typename
                id
                organization {
                  id
                  __typename
                }
              }
            }
          }
        },
        Parallel {
          Flatten(path: "reviews.@.author") {
            Fetch(service: "user") {
              {
                ... on User {
                  __typename
                  id
                  organization {
                    id
                  }
                }
              } =>
              {
                ... on User {
                  name
                }
              }
            },
          },
          Flatten(path: "reviews.@.author.organization") {
            Fetch(service: "user") {
              {
                ... on Organization {
                  __typename
                  id
                }
              } =>
              {
                ... on Organization {
                  name
                }
              }
            },
          },
        },
      },
    }
  `);
});
