import { expect } from 'chai';
import { describe, it } from 'mocha';

import { expectJSON } from '../__testUtils__/expectJSON.js';

import { graphql } from '../graphql.js';

import { StarWarsSchema as schema } from './starWarsSchema.js';

describe('Star Wars Query Tests', () => {
  describe('Basic Queries', () => {
    it('Correctly identifies R2-D2 as the hero of the Star Wars Saga', async () => {
      const source = `
        query HeroNameQuery {
          hero {
            name
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          hero: {
            name: 'R2-D2',
          },
        },
      });
    });

    it('Allows us to query for the ID and friends of R2-D2', async () => {
      const source = `
        query HeroNameAndFriendsQuery {
          hero {
            id
            name
            friends {
              name
            }
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          hero: {
            id: '2001',
            name: 'R2-D2',
            friends: [
              {
                name: 'Luke Skywalker',
              },
              {
                name: 'Han Solo',
              },
              {
                name: 'Leia Organa',
              },
            ],
          },
        },
      });
    });
  });

  describe('Nested Queries', () => {
    it('Allows us to query for the friends of friends of R2-D2', async () => {
      const source = `
        query NestedQuery {
          hero {
            name
            friends {
              name
              appearsIn
              friends {
                name
              }
            }
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          hero: {
            name: 'R2-D2',
            friends: [
              {
                name: 'Luke Skywalker',
                appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'],
                friends: [
                  {
                    name: 'Han Solo',
                  },
                  {
                    name: 'Leia Organa',
                  },
                  {
                    name: 'C-3PO',
                  },
                  {
                    name: 'R2-D2',
                  },
                ],
              },
              {
                name: 'Han Solo',
                appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'],
                friends: [
                  {
                    name: 'Luke Skywalker',
                  },
                  {
                    name: 'Leia Organa',
                  },
                  {
                    name: 'R2-D2',
                  },
                ],
              },
              {
                name: 'Leia Organa',
                appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'],
                friends: [
                  {
                    name: 'Luke Skywalker',
                  },
                  {
                    name: 'Han Solo',
                  },
                  {
                    name: 'C-3PO',
                  },
                  {
                    name: 'R2-D2',
                  },
                ],
              },
            ],
          },
        },
      });
    });
  });

  describe('Using IDs and query parameters to refetch objects', () => {
    it('Allows us to query characters directly, using their IDs', async () => {
      const source = `
        query FetchLukeAndC3POQuery {
          human(id: "1000") {
            name
          }
          droid(id: "2000") {
            name
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          human: {
            name: 'Luke Skywalker',
          },
          droid: {
            name: 'C-3PO',
          },
        },
      });
    });

    it('Allows us to create a generic query, then use it to fetch Luke Skywalker using his ID', async () => {
      const source = `
        query FetchSomeIDQuery($someId: String!) {
          human(id: $someId) {
            name
          }
        }
      `;
      const variableValues = { someId: '1000' };

      const result = await graphql({ schema, source, variableValues });
      expect(result).to.deep.equal({
        data: {
          human: {
            name: 'Luke Skywalker',
          },
        },
      });
    });

    it('Allows us to create a generic query, then use it to fetch Han Solo using his ID', async () => {
      const source = `
        query FetchSomeIDQuery($someId: String!) {
          human(id: $someId) {
            name
          }
        }
      `;
      const variableValues = { someId: '1002' };

      const result = await graphql({ schema, source, variableValues });
      expect(result).to.deep.equal({
        data: {
          human: {
            name: 'Han Solo',
          },
        },
      });
    });

    it('Allows us to create a generic query, then pass an invalid ID to get null back', async () => {
      const source = `
        query humanQuery($id: String!) {
          human(id: $id) {
            name
          }
        }
      `;
      const variableValues = { id: 'not a valid id' };

      const result = await graphql({ schema, source, variableValues });
      expect(result).to.deep.equal({
        data: {
          human: null,
        },
      });
    });
  });

  describe('Using aliases to change the key in the response', () => {
    it('Allows us to query for Luke, changing his key with an alias', async () => {
      const source = `
        query FetchLukeAliased {
          luke: human(id: "1000") {
            name
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          luke: {
            name: 'Luke Skywalker',
          },
        },
      });
    });

    it('Allows us to query for both Luke and Leia, using two root fields and an alias', async () => {
      const source = `
        query FetchLukeAndLeiaAliased {
          luke: human(id: "1000") {
            name
          }
          leia: human(id: "1003") {
            name
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          luke: {
            name: 'Luke Skywalker',
          },
          leia: {
            name: 'Leia Organa',
          },
        },
      });
    });
  });

  describe('Uses fragments to express more complex queries', () => {
    it('Allows us to query using duplicated content', async () => {
      const source = `
        query DuplicateFields {
          luke: human(id: "1000") {
            name
            homePlanet
          }
          leia: human(id: "1003") {
            name
            homePlanet
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          luke: {
            name: 'Luke Skywalker',
            homePlanet: 'Tatooine',
          },
          leia: {
            name: 'Leia Organa',
            homePlanet: 'Alderaan',
          },
        },
      });
    });

    it('Allows us to use a fragment to avoid duplicating content', async () => {
      const source = `
        query UseFragment {
          luke: human(id: "1000") {
            ...HumanFragment
          }
          leia: human(id: "1003") {
            ...HumanFragment
          }
        }

        fragment HumanFragment on Human {
          name
          homePlanet
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          luke: {
            name: 'Luke Skywalker',
            homePlanet: 'Tatooine',
          },
          leia: {
            name: 'Leia Organa',
            homePlanet: 'Alderaan',
          },
        },
      });
    });
  });

  describe('Using __typename to find the type of an object', () => {
    it('Allows us to verify that R2-D2 is a droid', async () => {
      const source = `
        query CheckTypeOfR2 {
          hero {
            __typename
            name
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          hero: {
            __typename: 'Droid',
            name: 'R2-D2',
          },
        },
      });
    });

    it('Allows us to verify that Luke is a human', async () => {
      const source = `
        query CheckTypeOfLuke {
          hero(episode: EMPIRE) {
            __typename
            name
          }
        }
      `;

      const result = await graphql({ schema, source });
      expect(result).to.deep.equal({
        data: {
          hero: {
            __typename: 'Human',
            name: 'Luke Skywalker',
          },
        },
      });
    });
  });

  describe('Reporting errors raised in resolvers', () => {
    it('Correctly reports error on accessing secretBackstory', async () => {
      const source = `
        query HeroNameQuery {
          hero {
            name
            secretBackstory
          }
        }
      `;

      const result = await graphql({ schema, source });
      expectJSON(result).toDeepEqual({
        data: {
          hero: {
            name: 'R2-D2',
            secretBackstory: null,
          },
        },
        errors: [
          {
            message: 'secretBackstory is secret.',
            locations: [{ line: 5, column: 13 }],
            path: ['hero', 'secretBackstory'],
          },
        ],
      });
    });

    it('Correctly reports error on accessing secretBackstory in a list', async () => {
      const source = `
        query HeroNameQuery {
          hero {
            name
            friends {
              name
              secretBackstory
            }
          }
        }
      `;

      const result = await graphql({ schema, source });
      expectJSON(result).toDeepEqual({
        data: {
          hero: {
            name: 'R2-D2',
            friends: [
              {
                name: 'Luke Skywalker',
                secretBackstory: null,
              },
              {
                name: 'Han Solo',
                secretBackstory: null,
              },
              {
                name: 'Leia Organa',
                secretBackstory: null,
              },
            ],
          },
        },
        errors: [
          {
            message: 'secretBackstory is secret.',
            locations: [{ line: 7, column: 15 }],
            path: ['hero', 'friends', 0, 'secretBackstory'],
          },
          {
            message: 'secretBackstory is secret.',
            locations: [{ line: 7, column: 15 }],
            path: ['hero', 'friends', 1, 'secretBackstory'],
          },
          {
            message: 'secretBackstory is secret.',
            locations: [{ line: 7, column: 15 }],
            path: ['hero', 'friends', 2, 'secretBackstory'],
          },
        ],
      });
    });

    it('Correctly reports error on accessing through an alias', async () => {
      const source = `
        query HeroNameQuery {
          mainHero: hero {
            name
            story: secretBackstory
          }
        }
      `;

      const result = await graphql({ schema, source });
      expectJSON(result).toDeepEqual({
        data: {
          mainHero: {
            name: 'R2-D2',
            story: null,
          },
        },
        errors: [
          {
            message: 'secretBackstory is secret.',
            locations: [{ line: 5, column: 13 }],
            path: ['mainHero', 'story'],
          },
        ],
      });
    });
  });
});