import { ObjectTypeDefinitionNode, DirectiveNode, parse } from 'graphql';
import { GraphQLTransform } from '../GraphQLTransform';
import { TransformerContext } from '../TransformerContext';
import { Transformer } from '../Transformer';
import { getDirectiveArguments, gql, removeAmplifyInput } from '../util';

class ValidObjectTransformer extends Transformer {
  constructor() {
    super(
      'ValidObjectTransformer',
      gql`
        directive @ObjectDirective on OBJECT
      `,
    );
  }

  public object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, acc: TransformerContext) => {
    return;
  };
}

class InvalidObjectTransformer extends Transformer {
  constructor() {
    super(
      'InvalidObjectTransformer',
      gql`
        directive @ObjectDirective on OBJECT
      `,
    );
  }
}

test('graphql transformer supports empty schema', () => {
  const emptySchema = ``;
  const transformer = new GraphQLTransform({
    transformers: [new ValidObjectTransformer()],
  });

  const out = transformer.transform(emptySchema);

  expect(out?.functions).toEqual({});
  expect(out?.resolvers).toEqual({});
});

test('graphql transformer validation happy case', () => {
  const validSchema = `type Post @ObjectDirective { id: ID! }`;
  const transformer = new GraphQLTransform({
    transformers: [new ValidObjectTransformer()],
  });
  const out = transformer.transform(validSchema);
  expect(out).toBeDefined();
});

test('graphql transformer validation. Transformer does not implement required method.', () => {
  const validSchema = `type Post @ObjectDirective { id: ID! }`;
  const transformer = new GraphQLTransform({
    transformers: [new InvalidObjectTransformer()],
  });
  try {
    transformer.transform(validSchema);
  } catch (e) {
    expect(e.name).toEqual('InvalidTransformerError');
  }
});

test('graphql transformer validation. Unknown directive.', () => {
  const invalidSchema = `type Post @UnknownDirective { id: ID! }`;
  const transformer = new GraphQLTransform({
    transformers: [new InvalidObjectTransformer()],
  });
  expect(() => transformer.transform(invalidSchema)).toThrowError(/Unknown directive/);
});

class PingTransformer extends Transformer {
  constructor() {
    super(
      'ValidObjectTransformer',
      gql`
        directive @ping(config: PingConfig) on OBJECT
        input PingConfig {
          url: String!
        }
      `,
    );
  }

  public object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, acc: TransformerContext) => {
    return;
  };
}

test('graphql transformer validation on bad shapes. @ping directive.', () => {
  const invalidSchema = `type Post @ping(config: { bad: "shape" }) { id: ID! }`;
  const transformer = new GraphQLTransform({
    transformers: [new PingTransformer()],
  });
  try {
    console.log(`Transforming: \n${invalidSchema}`);
    const out = transformer.transform(invalidSchema);
    expect(true).toEqual(false);
  } catch (e) {
    expect(e.name).toEqual('SchemaValidationError');
  }
});

test('graphql transformer returns correct number of arguments from directive', () => {
  const validSchema = `type Post @model(queries: { list: "listPost" }, mutations: {create: "createCustom"}) { name: String! }`;
  const transformer = new ValidObjectTransformer();
  const doc = parse(validSchema);
  const def = doc.definitions[0] as ObjectTypeDefinitionNode;
  const map: any = getDirectiveArguments(def.directives[0]);
  expect(map).not.toBeNull();
  expect(Object.keys(map)).toEqual(expect.arrayContaining(['mutations', 'queries']));
});

test('Remove amplify input should work', () => {
  const { schema } = removeAmplifyInput(`
    input AMPLIFY {
      globalAuthRule: AuthRule = { allow: public }
    }
    type Post @model { id: ID! }
  `);
  expect(schema).toMatchSnapshot();
});
