import { dedent } from '../support/testkit';

const selectors = {
  buttonGraphiQLPreflight: '[aria-label*="Preflight Script"]',
  buttonModalCy: 'preflight-modal-button',
  buttonToggleCy: 'toggle-preflight',
  buttonHeaders: '[data-name="headers"]',
  headersEditor: {
    textArea: '.graphiql-editor-tool .graphiql-editor:last-child textarea',
  },
  graphiql: {
    buttonExecute: '.graphiql-execute-button',
  },

  modal: {
    buttonSubmitCy: 'preflight-modal-submit',
  },
};

const data: { slug: string } = {
  slug: '',
};

beforeEach(() => {
  cy.clearLocalStorage().then(async () => {
    window.localStorage.setItem('hive:laboratory:type', 'graphiql');

    cy.task('seedTarget').then(({ slug, refreshToken }: any) => {
      cy.setCookie('sRefreshToken', refreshToken);
      data.slug = slug;
      cy.visit(`/${slug}/laboratory`);
      cy.get(selectors.buttonGraphiQLPreflight).click();
    });
  });
});

/** Helper function for setting the text within a monaco editor as typing manually results in flaky tests */
function setMonacoEditorContents(editorCyName: string, text: string) {
  const selectAllKeys = Cypress.platform == 'darwin' ? '{cmd}a' : '{ctrl}a';
  // Wait for Monaco to render, then select all existing content and replace it
  // via execCommand('insertText') which Monaco handles natively through beforeinput events.
  cy.dataCy(editorCyName).find('textarea').focus().type(selectAllKeys, { force: true });
  // cy.dataCy(editorCyName).find('textarea').focus().type('{ctrl+a}', { force: true });
  cy.document().then(doc => {
    doc.execCommand('insertText', false, text);
  });
}

function setEditorScript(script: string) {
  setMonacoEditorContents('preflight-editor', script);
}

describe('Laboratory > Preflight Script', () => {
  // https://github.com/graphql-hive/console/pull/6450
  it('regression: loads even if local storage is set to {}', () => {
    window.localStorage.setItem('hive:laboratory:environment', '{}');
    cy.visit(`/${data.slug}/laboratory`);
    cy.get(selectors.buttonGraphiQLPreflight).click();
  });
  it('mini script editor is read only', () => {
    cy.dataCy('toggle-preflight').click();
    // Wait loading disappears
    cy.dataCy('preflight-editor-mini').should('not.contain', 'Loading');
    // Click
    cy.dataCy('preflight-editor-mini').click();
    // And type
    cy.dataCy('preflight-editor-mini').within(() => {
      cy.get('textarea').type('🐝', { force: true });
    });
    cy.dataCy('preflight-editor-mini').should('have.text', 'Cannot edit in read-only editor');
  });
});

describe('Preflight Script Modal', () => {
  const script = 'console.log("Hello_world")';
  const env = '{"foo":123}';

  beforeEach(() => {
    cy.dataCy('preflight-modal-button').click();
    setMonacoEditorContents('env-editor', env);
  });

  it('save script and environment variables when submitting', () => {
    setEditorScript(script);
    cy.dataCy('preflight-modal-submit').click();
    cy.dataCy('env-editor-mini').should('have.text', env);
    cy.dataCy('toggle-preflight').click();
    cy.dataCy('preflight-editor-mini').should('have.text', script);
    cy.reload();
    cy.get('[aria-label*="Preflight Script"]').click();
    cy.dataCy('env-editor-mini').should('have.text', env);
    cy.dataCy('preflight-editor-mini').should('have.text', script);
  });

  it('logs show console/error information', () => {
    setEditorScript(script);
    cy.dataCy('run-preflight').click();
    cy.dataCy('console-output').should('contain', 'log: Hello_world (1:1)');

    setEditorScript(
      `console.info(1)
console.warn(true)
console.error('Fatal')
throw new TypeError('Test')`,
    );

    cy.dataCy('run-preflight').click();
    // First log previous log message
    cy.dataCy('console-output').should('contain', 'log: Hello_world (1:1)');
    // After the new logs
    cy.dataCy('console-output').should(
      'contain',
      ['info: 1 (1:1)', 'warn: true (2:1)', 'error: Fatal (3:1)', 'error: Test (4:7)'].join(''),
    );
  });

  it('prompt and pass the awaited response', () => {
    setEditorScript(script);

    cy.dataCy('run-preflight').click();
    cy.dataCy('console-output').should('contain', 'log: Hello_world (1:1)');

    setEditorScript(
      dedent`
        const username = await lab.prompt('Enter your username');
        console.info(username);
      `,
    );

    cy.dataCy('run-preflight').click();
    cy.dataCy('prompt').get('input').type('test-username');
    cy.dataCy('prompt').get('form').submit();

    // First log previous log message
    cy.dataCy('console-output').should('contain', 'log: Hello_world (1:1)');
    // After the new logs
    cy.dataCy('console-output').should(
      'contain',
      dedent`
        info: test-username (2:1)
      `,
    );
  });

  it('prompt and cancel', () => {
    setEditorScript(script);

    cy.dataCy('run-preflight').click();
    cy.dataCy('console-output').should('contain', 'log: Hello_world (1:1)');

    setEditorScript(
      dedent`
        const username = await lab.prompt('Enter your username');
        console.info(username);
      `,
    );

    cy.dataCy('run-preflight').click();
    cy.dataCy('prompt').get('input').type('test-username');
    cy.dataCy('prompt').get('[data-cy="prompt-cancel"]').click();

    // First log previous log message
    cy.dataCy('console-output').should('contain', 'log: Hello_world (1:1)');
    // After the new logs
    cy.dataCy('console-output').should(
      'contain',
      dedent`
        info: null (2:1)
      `,
    );
  });

  it('script execution updates environment variables', () => {
    setEditorScript(`lab.environment.set('my-test', "TROLOLOL")`);

    cy.dataCy('run-preflight').click();
    cy.dataCy('env-editor').should(
      'include.text',
      // replace space with &nbsp;
      '{  "foo": 123,  "my-test": "TROLOLOL"}'.replaceAll(' ', '\xa0'),
    );
  });

  it('`crypto-js` can be used for generating hashes', () => {
    setEditorScript('console.log(lab.CryptoJS.SHA256("🐝"))');
    cy.dataCy('run-preflight').click();
    cy.dataCy('console-output').should('contain', 'info: Using crypto-js version:');
    cy.dataCy('console-output').should(
      'contain',
      'log: d5b51e79e4be0c4f4d6b9a14e16ca864de96afe68459e60a794e80393a4809e8',
    );
  });
});

describe('Execution', () => {
  it('result.request.headers are added to the graphiql request base headers', () => {
    // Setup Preflight Script
    const preflightHeaders = {
      foo: 'bar',
    };
    cy.dataCy(selectors.buttonToggleCy).click();
    cy.dataCy(selectors.buttonModalCy).click();
    setEditorScript(`lab.request.headers.append('foo', '${preflightHeaders.foo}')`);
    cy.dataCy(selectors.modal.buttonSubmitCy).click();
    // Run GraphiQL
    cy.intercept({ headers: preflightHeaders }).as('request');
    cy.get(selectors.graphiql.buttonExecute).click();
    cy.wait('@request');
  });

  it('result.request.headers take precedence over graphiql request base headers', () => {
    // Integrity Check: Ensure the header we think we're overriding is actually there to override.
    // We achieve this by asserting a sent GraphiQL request includes the certain header and assume
    // if its there once its there every time.
    const baseHeaders = {
      accept: 'application/json, multipart/mixed',
    };
    // Wait for GraphiQL editor to be ready before clicking execute
    cy.get('.graphiql-query-editor .cm-s-graphiql').should('exist');
    cy.intercept('POST', '**/api/lab/**').as('integrityCheck');
    cy.get(selectors.graphiql.buttonExecute).click();
    cy.wait('@integrityCheck').its('request.headers.accept').should('include', baseHeaders.accept);
    // Setup Preflight Script
    const preflightHeaders = {
      accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
    };
    cy.dataCy(selectors.buttonToggleCy).click();
    cy.dataCy(selectors.buttonModalCy).click();
    setEditorScript(`lab.request.headers.append('accept', '${preflightHeaders.accept}')`);
    cy.dataCy(selectors.modal.buttonSubmitCy).click();
    // Run GraphiQL
    cy.intercept({ headers: preflightHeaders }).as('request');
    cy.get(selectors.graphiql.buttonExecute).click();
    cy.wait('@request', { timeout: 10_000 });
  });

  it('result.request.headers are NOT substituted with environment variables', () => {
    const barEnVarInterpolation = '{{bar}}';
    // Setup Static Headers
    const staticHeaders = {
      foo_static: barEnVarInterpolation,
    };
    cy.get(selectors.buttonHeaders).click();
    cy.get(selectors.headersEditor.textArea).type(JSON.stringify(staticHeaders), {
      force: true,
      parseSpecialCharSequences: false,
    });
    // Setup Preflight Script
    const environmentVariables = {
      bar: 'BAR_VALUE',
    };
    const preflightHeaders = {
      foo_preflight: barEnVarInterpolation,
    };
    cy.dataCy(selectors.buttonToggleCy).click();
    cy.dataCy(selectors.buttonModalCy).click();
    setEditorScript(`
      lab.environment.set('bar', '${environmentVariables.bar}')
      lab.request.headers.append('foo_preflight', '${preflightHeaders.foo_preflight}')
    `);
    cy.dataCy(selectors.modal.buttonSubmitCy).click();
    // Run GraphiQL
    cy.intercept({
      headers: {
        ...preflightHeaders,
        foo_static: environmentVariables.bar,
      },
    }).as('request');
    cy.get(selectors.graphiql.buttonExecute).click();
    cy.wait('@request', { timeout: 10_000 });
  });

  it('header placeholders are substituted with environment variables', () => {
    cy.dataCy('toggle-preflight').click();
    cy.get('[data-name="headers"]').click();
    cy.get('.graphiql-editor-tool .graphiql-editor:last-child textarea').type(
      '{ "__test": "{{foo}} bar {{nonExist}}" }',
      {
        force: true,
        parseSpecialCharSequences: false,
      },
    );
    setMonacoEditorContents('env-editor-mini', '{"foo":"injected"}');

    cy.intercept({
      method: 'POST',
      headers: {
        __test: 'injected bar {{nonExist}}',
      },
    }).as('post');
    cy.get('.graphiql-execute-button').click();
    cy.wait('@post');
  });

  it('executed script updates update env editor and substitute headers', () => {
    cy.dataCy('toggle-preflight').click();
    cy.get('[data-name="headers"]').click();
    cy.get('.graphiql-editor-tool .graphiql-editor:last-child textarea').type(
      '{ "__test": "{{foo}}" }',
      {
        force: true,
        parseSpecialCharSequences: false,
      },
    );
    cy.dataCy('preflight-modal-button').click();
    setMonacoEditorContents('preflight-editor', `lab.environment.set('foo', '92')`);
    cy.dataCy('preflight-modal-submit').click();

    cy.intercept({
      method: 'POST',
      headers: {
        __test: '92',
      },
    }).as('post');
    cy.get('.graphiql-execute-button').click();
    cy.wait('@post');
  });

  it('execute, prompt and use it in headers', () => {
    cy.dataCy('toggle-preflight').click();

    cy.get('[data-name="headers"]').click();
    cy.get('[data-name="headers"]').click();
    cy.get('.graphiql-editor-tool .graphiql-editor:last-child textarea').type(
      '{ "__test": "{{username}}" }',
      {
        force: true,
        parseSpecialCharSequences: false,
      },
    );

    cy.dataCy('preflight-modal-button').click();
    setMonacoEditorContents(
      'preflight-editor',
      dedent`
      const username = await lab.prompt('Enter your username');
      lab.environment.set('username', username);
    `,
    );
    cy.dataCy('preflight-modal-submit').click();

    cy.intercept({
      method: 'POST',
      headers: {
        __test: 'foo',
      },
    }).as('post');
    cy.get('.graphiql-execute-button').click();

    cy.dataCy('prompt').get('input').type('foo');
    cy.dataCy('prompt').get('form').submit();

    cy.wait('@post');
  });

  it('disabled script is not executed', () => {
    cy.get('[data-name="headers"]').click();
    cy.get('.graphiql-editor-tool .graphiql-editor:last-child textarea').type(
      '{ "__test": "{{foo}}" }',
      {
        force: true,
        parseSpecialCharSequences: false,
      },
    );
    cy.dataCy('preflight-modal-button').click();
    setMonacoEditorContents('preflight-editor', `lab.environment.set('foo', 92)`);
    setMonacoEditorContents('env-editor', `{"foo":10}`);

    cy.dataCy('preflight-modal-submit').click();

    cy.intercept({
      method: 'POST',
      headers: {
        __test: '10',
      },
    }).as('post');
    cy.get('.graphiql-execute-button').click();
    cy.wait('@post');
  });

  it('logs are visible when opened', () => {
    cy.dataCy('toggle-preflight').click();

    cy.dataCy('preflight-modal-button').click();
    setMonacoEditorContents(
      'preflight-editor',
      dedent`
        console.info(1)
        console.warn(true)
        console.error('Fatal')
        throw new TypeError('Test')
        `,
    );
    cy.dataCy('preflight-modal-submit').click();

    cy.intercept({
      method: 'POST',
    }).as('post');

    // shows no logs before executing
    cy.get('#preflight-logs button[data-cy="trigger"]').click({
      // it's because the button is not fully visible on the screen
      force: true,
    });
    cy.get('#preflight-logs [data-cy="logs"]').should(
      'contain',
      ['No logs available', 'Execute a query to see logs'].join(''),
    );

    cy.get('.graphiql-execute-button').click();
    cy.wait('@post');

    cy.get('#preflight-logs [data-cy="logs"]').should(
      'contain',
      [
        'log: Running script...',
        'info: 1 (1:1)',
        'warn: true (2:1)',
        'error: Fatal (3:1)',
        'error: Test (4:7)',
        'log: Script failed',
      ].join(''),
    );
  });

  it('logs are cleared when requested', () => {
    cy.dataCy('toggle-preflight').click();

    cy.dataCy('preflight-modal-button').click();
    setMonacoEditorContents(
      'preflight-editor',
      dedent`
        console.info(1)
        console.warn(true)
        console.error('Fatal')
        throw new TypeError('Test')
        `,
    );
    cy.dataCy('preflight-modal-submit').click();

    cy.intercept({
      method: 'POST',
    }).as('post');
    cy.get('.graphiql-execute-button').click();
    cy.wait('@post');

    // open logs
    cy.get('#preflight-logs button[data-cy="trigger"]').click({
      // it's because the button is not fully visible on the screen
      force: true,
    });

    cy.get('#preflight-logs [data-cy="logs"]').should(
      'contain',
      [
        'log: Running script...',
        'info: 1 (1:1)',
        'warn: true (2:1)',
        'error: Fatal (3:1)',
        'error: Test (4:7)',
        'log: Script failed',
      ].join(''),
    );

    cy.get('#preflight-logs button[data-cy="erase-logs"]').click();
    cy.get('#preflight-logs [data-cy="logs"]').should(
      'contain',
      ['No logs available', 'Execute a query to see logs'].join(''),
    );
  });
});
