import { describe, it } from 'node:test';

import { expect } from 'chai';

import { expectPromise } from '../../__testUtils__/expectPromise.ts';
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.ts';

import { parse } from '../../language/parser.ts';

import {
  GraphQLDeferDirective,
  GraphQLList,
  GraphQLObjectType,
  GraphQLStreamDirective,
  GraphQLString,
} from '../../type/index.ts';
import { GraphQLSchema } from '../../type/schema.ts';

import { execute } from '../execute.ts';

describe('Original execute errors on experimental @defer and @stream directives', () => {
  it('errors when using original execute with schemas including experimental @defer directive', () => {
    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Q',
        fields: {
          a: { type: GraphQLString },
        },
      }),
      directives: [GraphQLDeferDirective],
    });
    const document = parse('query Q { a }');

    expect(() => execute({ schema, document })).to.throw(
      'The provided schema unexpectedly contains experimental directives (@defer or @stream). These directives may only be utilized if experimental execution features are explicitly enabled.',
    );
  });

  it('errors when using original execute with schemas including experimental @stream directive', () => {
    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Q',
        fields: {
          a: { type: GraphQLString },
        },
      }),
      directives: [GraphQLStreamDirective],
    });
    const document = parse('query Q { a }');

    expect(() => execute({ schema, document })).to.throw(
      'The provided schema unexpectedly contains experimental directives (@defer or @stream). These directives may only be utilized if experimental execution features are explicitly enabled.',
    );
  });

  it('original execute function throws error if anything is deferred and everything else is sync', () => {
    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          hero: {
            type: new GraphQLObjectType({
              name: 'Hero',
              fields: {
                id: { type: GraphQLString },
              },
            }),
          },
        },
      }),
    });
    const doc = `
      query Deferred {
        ... @defer { hero { id } }
      }
    `;
    expect(() =>
      execute({
        schema,
        document: parse(doc),
        rootValue: {},
      }),
    ).to.throw(
      'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)',
    );
  });

  it('original execute function resolves to error if anything is deferred and something else is async', async () => {
    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          hero: {
            type: new GraphQLObjectType({
              name: 'Hero',
              fields: {
                id: { type: GraphQLString },
              },
            }),
          },
        },
      }),
    });
    const doc = `
      query Deferred {
        hero { name ... @defer { id } }
      }
    `;
    await expectPromise(
      execute({
        schema,
        document: parse(doc),
        rootValue: {
          hero: Promise.resolve({
            id: '1',
            name: async () => {
              await resolveOnNextTick();
              return 'slow';
            },
          }),
        },
      }),
    ).toRejectWith(
      'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)',
    );
  });

  it('original execute function throws error if anything is streamed and everything else is sync', () => {
    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          scalarList: { type: new GraphQLList(GraphQLString) },
          friendList: {
            type: new GraphQLList(
              new GraphQLObjectType({
                name: 'Friend',
                fields: {
                  name: { type: GraphQLString },
                },
              }),
            ),
          },
        },
      }),
    });
    const doc = `
      query {
        scalarList
        friendList @stream { name }
      }
    `;
    expect(() =>
      execute({
        schema,
        document: parse(doc),
        rootValue: {
          scalarList: ['apple', 'banana', 'coconut'],
          friendList: [{ name: 'Alice' }, { name: 'Bob' }],
        },
      }),
    ).to.throw(
      'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)',
    );
  });
  it('original execute function resolves to error if anything is streamed and something else is async', async () => {
    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: {
          nested: {
            type: new GraphQLObjectType({
              name: 'Nested',
              fields: {
                scalarList: { type: new GraphQLList(GraphQLString) },
                friendList: {
                  type: new GraphQLList(
                    new GraphQLObjectType({
                      name: 'Friend',
                      fields: {
                        name: { type: GraphQLString },
                      },
                    }),
                  ),
                },
              },
            }),
          },
        },
      }),
    });
    const doc = `
      query {
        nested {
          scalarList
          friendList @stream { name }
        }
      }
    `;
    await expectPromise(
      execute({
        schema,
        document: parse(doc),
        rootValue: {
          nested: Promise.resolve({
            scalarList: Promise.resolve(['apple', 'banana', 'coconut']),
            friendList: [{ name: 'Alice' }, { name: 'Bob' }],
          }),
        },
      }),
    ).toRejectWith(
      'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)',
    );
  });
});