/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
import { makeExecutableSchema } from '@graphql-tools/schema';

const defaultResolveType = (obj, _context, _info) => {
  return obj.__typename;
};

const typenameResolver = {
  __resolveType: defaultResolveType,
};

const getByDataCy = (selector: string) => {
  return cy.get(`[data-cy=${selector}]`);
};

const mockGraphql = (options?: any) => {
  cy.task('getSchema').then((schemaString: string) => {
    const resolverMap = {
      DeleteSnapshotResult: typenameResolver,
      UpdateSnapshotResult: typenameResolver,
      RedeemCouponResult: typenameResolver,
      HighlightableNode: {
        __resolveType: defaultResolveType,
      },
    };
    const schema = makeExecutableSchema({
      typeDefs: schemaString,
      resolvers: resolverMap,
    });

    const schemaWithMocks = addMocksToSchema({
      schema,
      mocks,
      preserveResolvers: true,
    });

    let currentOperations = options && options.operations ? options.operations : {};

    // rebuilding what was in cypress-graphql-mock package here, because now we can just intercept the graphql call instead of messing with fetch
    cy.intercept('POST', '/api/graphql', (req) => {
      const { operationName, query, variables } = req.body;
      req.alias = operationName;
      return getRootValue(currentOperations, operationName, variables)
        .then((rootValue) => {
          if (!rootValue) {
            return req;
          }

          if (
            // Additional checks here because of transpilation.
            // We will lose instanceof if we are not using specific babel plugin, or using pure TS to compile front-end
            rootValue instanceof GraphQLError ||
            rootValue.constructor === GraphQLError ||
            rootValue.constructor.name === 'GraphQLError'
          ) {
            return req.reply({
              body: {
                data: {},
                errors: [rootValue],
              },
            });
          }

          graphql({
            schema: schemaWithMocks,
            source: query,
            variableValues: variables,
            operationName,
            rootValue,
          }).then(
            (result) => {
              req.reply({
                ...result,
              });
            },
            (e) => {
              console.error(e);
            }
          );
        })
        .catch((e) => {
          console.error(e);
          req.reply({ statusCode: 500, body: { errors: ['Internal server error'] } });
        });
    }).as('graphqlRequest');

    cy.wrap({
      setOperations: (options: any) => {
        currentOperations = {
          ...currentOperations,
          ...options.operations,
        };
      },
    }).as('mockGraphqlOps');
  });
};

const mockGraphqlOps = (options) => {
  cy.get('@mockGraphqlOps').invoke('setOperations' as any, options);
};

const login = (username: string, password: string, visitLogin = false) => {
  if (visitLogin) {
    cy.visit('/beta-login');
  }

  if (username !== '') {
    cy.get('[data-cy=email-input]').type(username);
  }

  if (password !== '') {
    cy.get('[data-cy=password-input]').type(password);
  }
  cy.get('[data-cy=login-button]').click();
};

const fakeLogin = () => {
  cy.log('Logging in (fake)');
  cy.setCookie('loginStatus', 'true');
};

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Login via API call to the GraphQL endpoint, without calling the frontend. Faster than the other login.
       * @param   username
       * @param   password
       * @example
       * cy.apolloLogin('ross.geller', 'test')
       */
      apolloLogin(username: string, password: string): Chainable<any>;

      /**
       * Selects an element based on the `data-cy=xxx` attribute
       * @param selector - The value of the data-cy attribute to select
       * @example
       * cy.getByDataCy('my-new-button')
       */
      // getByDataCy(selector: string): Chainable<Element>;
      getByDataCy: typeof getByDataCy;

      selectClass(schoolClass: string): void;

      login: typeof login;

      fakeLogin: typeof fakeLogin;

      isSubmissionReadOnly(myText: string): void;

      openSidebar(): void;

      setup(): void;

      mockGraphql: typeof mockGraphql;
      mockGraphqlOps: typeof mockGraphqlOps;
    }
  }
}
// installed a fork of the original package, because of this issue:
// https://github.com/tgriesser/cypress-graphql-mock/issues/23
// todo: once above issue is fixed, go back to the original repo -> npm install cypress-graphql-mock
// import 'cypress-graphql-mock';
import mocks from '../fixtures/mocks';
import { addMocksToSchema } from '@graphql-tools/mock';
import { graphql, GraphQLError } from 'graphql';

Cypress.Commands.add('apolloLogin', (username, password) => {
  const payload = {
    operationName: 'BetaLogin',
    variables: {
      input: {
        usernameInput: username,
        passwordInput: password,
      },
    },
    query:
      'mutation BetaLogin($input: BetaLoginInput!) {\n  betaLogin(input: $input) {\n    success\n    __typename\n  }\n}\n',
  };

  cy.request({
    method: 'POST',
    url: '/api/graphql-public/',
    body: payload,
  });
});

// todo: replace with apollo call
Cypress.Commands.add('login', login);

Cypress.Commands.add('getByDataCy', getByDataCy);

Cypress.Commands.add('selectClass', (schoolClass) => {
  cy.getByDataCy('user-widget-avatar').click();
  cy.getByDataCy('class-selection').click();
  cy.getByDataCy('class-selection-entry').contains(schoolClass).click();
});

Cypress.Commands.add('fakeLogin', fakeLogin);

Cypress.Commands.add('isSubmissionReadOnly', (myText) => {
  cy.get('.submission-form__textarea--readonly').as('textarea');

  cy.get('@textarea').invoke('val').should('contain', myText);
  cy.get('@textarea').should('have.attr', 'readonly');
  cy.getByDataCy('submission-form-submit').should('not.exist');
});

Cypress.Commands.add('openSidebar', () => {
  cy.getByDataCy('user-widget-avatar').click();
});

Cypress.Commands.add('setup', () => {
  cy.fakeLogin();
  cy.viewport('macbook-15');
  cy.mockGraphql();
});

Cypress.Commands.add('mockGraphql', mockGraphql);

Cypress.Commands.add('mockGraphqlOps', mockGraphqlOps);

const getRootValue = async (allOperations: any, operationName: string, variables: any) => {
  const operation = allOperations[operationName];
  if (typeof operation === 'function') {
    return Promise.resolve().then(() => operation(variables));
  }
  return Promise.resolve(operation);
};
