/*
 * Copyright 2018-2022 Elyra Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

describe('Pipeline Editor tests', () => {
  beforeEach(() => {
    cy.deleteFile('helloworld.yaml');
    cy.deleteFile('*.pipeline'); // delete pipeline files used for testing

    cy.bootstrapFile('invalid.pipeline');
    cy.bootstrapFile('helloworld.pipeline');
    cy.bootstrapFile('helloworld.ipynb');
    cy.exec('jupyter trust build/cypress-tests/helloworld.ipynb');
    cy.bootstrapFile('helloworld.py');
    cy.bootstrapFile('helloworld.r');
    cy.bootstrapFile('invalid.txt');

    cy.resetJupyterLab();
  });

  afterEach(() => {
    cy.deleteFile('helloworld.ipynb'); // delete notebook file used for testing
    cy.deleteFile('helloworld.py'); // delete python file used for testing
    cy.deleteFile('output.txt'); // delete output files generated by tests
    cy.deleteFile('*.pipeline'); // delete pipeline files used for testing
    cy.deleteFile('helloworld.yaml');
    cy.deleteFile('invalid.txt');

    // delete complex test directories
    cy.deleteFile('pipelines');
    cy.deleteFile('scripts');

    // delete runtime configurations used for testing
    cy.exec('elyra-metadata remove runtimes --name=kfp_test_runtime', {
      failOnNonZeroExit: false
    });
    cy.exec('elyra-metadata remove runtimes --name=airflow_test_runtime', {
      failOnNonZeroExit: false
    });

    // delete example catalogs used for testing
    cy.exec(
      'elyra-metadata remove component-catalogs --name=example_components',
      {
        failOnNonZeroExit: false
      }
    );
  });

  // TODO: Fix Test is actually failing
  // it('empty editor should have disabled buttons', () => {
  //   cy.focusPipelineEditor();

  //   const disabledButtons = [
  //     '.run-action',
  //     '.export-action',
  //     '.clear-action',
  //     '.undo-action',
  //     '.redo-action',
  //     '.cut-action',
  //     '.copy-action',
  //     '.paste-action',
  //     '.deleteSelectedObjects-action',
  //     '.arrangeHorizontally-action',
  //     '.arrangeVertically-action'
  //   ];
  //   checkDisabledToolbarButtons(disabledButtons);

  //   const enabledButtons = [
  //     '.save-action',
  //     '.openRuntimes-action',
  //     '.createAutoComment-action'
  //   ];
  //   checkEnabledToolbarButtons(enabledButtons);

  //   closePipelineEditor();
  // });

  it('should block unsupported files', () => {
    cy.createPipeline();
    cy.dragAndDropFileToPipeline('invalid.txt');

    // check for unsupported files dialog message
    cy.findByText(/unsupported file/i).should('be.visible');

    // dismiss dialog
    cy.contains('OK').click();
  });

  it('populated editor should have enabled buttons', () => {
    cy.createPipeline();

    cy.checkTabMenuOptions('Pipeline');

    cy.addFileToPipeline('helloworld.ipynb'); // add Notebook
    cy.addFileToPipeline('helloworld.py'); // add Python Script
    cy.addFileToPipeline('helloworld.r'); // add R Script

    // check buttons
    const disabledButtons = [/redo/i, /cut/i, /copy/i, /paste/i, /delete/i];
    checkDisabledToolbarButtons(disabledButtons);

    const enabledButtons = [
      /run pipeline/i,
      /save pipeline/i,
      /export pipeline/i,
      /clear/i,
      /open runtimes/i,
      /open runtime images/i,
      /open component catalogs/i,
      /undo/i,
      /add comment/i,
      /arrange horizontally/i,
      /arrange vertically/i
    ];
    checkEnabledToolbarButtons(enabledButtons);
  });

  it('matches complex pipeline snapshot', () => {
    cy.bootstrapFile('pipelines/consumer.ipynb');
    cy.bootstrapFile('pipelines/create-source-files.py');
    cy.bootstrapFile('pipelines/producer-script.py');
    cy.bootstrapFile('pipelines/producer.ipynb');
    cy.bootstrapFile('scripts/setup.py');
    cy.bootstrapFile('scripts/setup.txt');

    // Do this all manually because our command doesn't support directories yet
    cy.openDirectory('pipelines');
    cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
    cy.openFile('complex.pipeline');
    cy.get('.common-canvas-drop-div');
    // wait an additional 300ms for the list of items to settle
    cy.wait(300);

    cy.addFileToPipeline('producer.ipynb');
    cy.addFileToPipeline('consumer.ipynb');

    cy.get('.jp-BreadCrumbs-home').click();
    cy.openDirectory('scripts');

    cy.addFileToPipeline('setup.py');

    cy.get('.jp-BreadCrumbs-home').click();
    cy.openDirectory('pipelines');

    cy.addFileToPipeline('create-source-files.py');
    cy.addFileToPipeline('producer-script.py');

    cy.get('#jp-main-dock-panel').within(() => {
      // producer props
      cy.findByText('producer.ipynb').rightclick();
      cy.findByRole('menuitem', { name: /properties/i }).click();
      cy.get('[data-id="properties-elyra_filename"]').within(() => {
        cy.findByRole('button', { name: /browse/i }).click();
      });
    });

    cy.get('.elyra-browseFileDialog').within(() => {
      cy.openDirectory('producer.ipynb');
    });

    cy.get('#jp-main-dock-panel').within(() => {
      cy.get('[data-id="properties-elyra_outputs"]').within(() => {
        cy.findByRole('button', { name: /add item/i }).click();
        cy.focused().type('output-1.csv');
        cy.contains('OK').click();

        cy.findByRole('button', { name: /add item/i }).click();
        cy.focused().type('output-2.csv');
        cy.contains('OK').click();
      });
      cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
        cy.findByRole('button').click();
        cy.findByRole('option', { name: /anaconda/i }).click();
      });

      // consumer props
      cy.findByText('consumer.ipynb').click();
      cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
        cy.findByRole('button').click();
        cy.findByRole('option', { name: /anaconda/i }).click();
      });

      // setup props
      cy.findByText('setup.py').click();
      cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
        cy.findByRole('button').click();
        cy.findByRole('option', { name: /anaconda/i }).click();
      });
      cy.get('[data-id="properties-elyra_dependencies"]').within(() => {
        cy.findByRole('button', { name: /browse/i }).click();
      });
    });

    // choosing dependencies happens outside of canvas
    cy.get('.elyra-browseFileDialog').within(() => {
      cy.openDirectory('setup.txt');
    });

    // back in canvas
    cy.get('#jp-main-dock-panel').within(() => {
      // create-source-files props
      cy.findByText('create-source-files.py').click();
      cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
        cy.findByRole('button').click();
        cy.findByRole('option', { name: /anaconda/i }).click();
      });
      cy.get('[data-id="properties-elyra_outputs"]').within(() => {
        cy.findByRole('button', { name: /add item/i }).click();
        cy.focused().type('input-1.csv');
        cy.contains('OK').click();

        cy.findByRole('button', { name: /add item/i }).click();
        cy.focused().type('input-2.csv');
        cy.contains('OK').click();
      });

      // producer-script props
      cy.findByText('producer-script.py').click();
      cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
        cy.findByRole('button').click();
        cy.findByRole('option', { name: /anaconda/i }).click();
      });
      cy.get('[data-id="properties-elyra_outputs"]').within(() => {
        cy.findByRole('button', { name: /add item/i }).click();
        cy.focused().type('output-3.csv');
        cy.contains('OK').click();

        cy.findByRole('button', { name: /add item/i }).click();
        cy.focused().type('output-4.csv');
        cy.contains('OK').click();
      });
    });

    cy.savePipeline();

    cy.readFile(
      'build/cypress-tests/pipelines/complex.pipeline'
    ).matchesSnapshot();
  });

  it('matches empty pipeline snapshot', () => {
    cy.createPipeline({ name: 'empty.pipeline' });

    cy.addFileToPipeline('helloworld.ipynb');

    cy.get('#jp-main-dock-panel').within(() => {
      cy.findByText('helloworld.ipynb').rightclick();
      cy.findByRole('menuitem', { name: /delete/i }).click();
    });

    cy.savePipeline();

    cy.readFile('build/cypress-tests/empty.pipeline').matchesSnapshot();
  });

  it('matches simple pipeline snapshot', () => {
    cy.createPipeline({ name: 'simple.pipeline' });

    cy.addFileToPipeline('helloworld.ipynb');

    cy.get('#jp-main-dock-panel').within(() => {
      cy.findByText('helloworld.ipynb');
    });

    cy.savePipeline();

    cy.readFile('build/cypress-tests/simple.pipeline').matchesSnapshot();
  });

  it('should open notebook on double-clicking the node', () => {
    // Open a pipeline in root directory
    cy.openFile('helloworld.pipeline');

    // Open notebook node with double-click
    cy.get('.common-canvas-drop-div').within(() => {
      cy.findByText('helloworld.ipynb').dblclick();
    });

    cy.findAllByRole('tab', { name: 'helloworld.ipynb' }).should('exist');

    // close tabs
    cy.closeTab(-1); // notebook tab
    cy.closeTab(-1); // pipeline tab

    // Open a pipeline in a subfolder
    cy.bootstrapFile('pipelines/producer.ipynb');
    cy.openDirectory('pipelines');
    cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
    cy.openFile('complex.pipeline');
    cy.get('.common-canvas-drop-div');
    cy.wait(300);
    cy.addFileToPipeline('producer.ipynb');
    cy.wait(300);

    // Open notebook node with double-click
    cy.get('#jp-main-dock-panel').within(() => {
      cy.findByText('producer.ipynb').dblclick();
    });

    cy.findAllByRole('tab', { name: 'producer.ipynb' }).should('exist');
  });

  it('should open notebook from node right-click menu', () => {
    // Open a pipeline in root directory
    cy.openFile('helloworld.pipeline');

    // Open notebook node with right-click menu
    cy.get('#jp-main-dock-panel').within(() => {
      cy.findByText('helloworld.ipynb').rightclick();
      cy.findByRole('menuitem', { name: /open file/i }).click();
    });

    cy.findAllByRole('tab', { name: 'helloworld.ipynb' }).should('exist');

    // close tabs
    cy.closeTab(-1); // notebook tab
    cy.closeTab(-1); // pipeline tab

    // Open a pipeline in a subfolder
    cy.bootstrapFile('pipelines/producer.ipynb');
    cy.openDirectory('pipelines');
    cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
    cy.openFile('complex.pipeline');
    cy.get('.common-canvas-drop-div');
    cy.wait(300);
    cy.addFileToPipeline('producer.ipynb');

    // Open notebook node with right-click menu
    cy.get('#jp-main-dock-panel').within(() => {
      cy.findByText('producer.ipynb').rightclick();
      cy.findByRole('menuitem', { name: /open file/i }).click();
    });

    cy.findAllByRole('tab', { name: 'producer.ipynb' }).should('exist');
  });

  it('should save runtime configuration', () => {
    cy.createPipeline();

    // Create kfp runtime configuration
    cy.createRuntimeConfig({ type: 'kfp' });

    // Create airflow runtime configuration
    cy.createRuntimeConfig({ type: 'airflow' });

    // validate runtimes are now available
    cy.get('#elyra-metadata\\:runtimes').within(() => {
      cy.findByText(/kfp test runtime/i).should('exist');
      cy.findByText(/airflow test runtime/i).should('exist');
    });
  });

  it('should fail to run invalid pipeline', () => {
    // opens pipeline from the file browser
    cy.openFile('invalid.pipeline');

    // try to run invalid pipeline
    cy.findByRole('button', { name: /run pipeline/i }).click();

    cy.findByText(/failed run:/i).should('be.visible');
  });

  it('should run pipeline after adding runtime image', () => {
    cy.createPipeline();

    cy.addFileToPipeline('helloworld.ipynb'); // add Notebook

    cy.get('#jp-main-dock-panel').within(() => {
      cy.findByText('helloworld.ipynb').rightclick();

      cy.findByRole('menuitem', { name: /properties/i }).click();

      // Adds runtime image to new node
      // TODO we should use the `for` attribute for the label
      cy.get('#downshift-0-toggle-button').click();

      cy.findByRole('option', { name: /anaconda/i }).click();
    });

    cy.savePipeline();

    cy.findByRole('button', { name: /run pipeline/i }).click();

    cy.findByLabelText(/pipeline name/i).should('have.value', 'untitled');
    cy.findByLabelText(/runtime platform/i).should(
      'have.value',
      '__elyra_local__'
    );

    // execute
    cy.contains('OK').click();

    // validate job was executed successfully, this can take a while in ci
    cy.findByText(/job execution succeeded/i, { timeout: 30000 }).should(
      'be.visible'
    );
    // dismiss 'Job Succeeded' dialog
    cy.contains('OK').click();
  });

  it('should run pipeline with env vars and output files', () => {
    cy.openFile('helloworld.pipeline');

    cy.findByRole('button', { name: /run pipeline/i }).click();

    cy.findByLabelText(/pipeline name/i).should('have.value', 'helloworld');
    cy.findByLabelText(/runtime platform/i).should(
      'have.value',
      '__elyra_local__'
    );

    // execute
    cy.contains('OK').click();

    // validate job was executed successfully, this can take a while in ci
    cy.findByText(/job execution succeeded/i, { timeout: 30000 }).should(
      'be.visible'
    );
    // dismiss 'Job Succeeded' dialog
    cy.contains('OK').click();

    cy.readFile('build/cypress-tests/output.txt').should(
      'be.equal',
      'TEST_ENV_1=1\nTEST_ENV_2=2\n'
    );
  });

  it('should fail to export invalid pipeline', () => {
    // Copy invalid pipeline

    cy.openFile('invalid.pipeline');

    cy.findByRole('button', { name: /export pipeline/i }).click();

    cy.findByText(/failed export:/i).should('be.visible');
  });

  it('should export pipeline as yaml', () => {
    // Install runtime configuration
    cy.installRuntimeConfig({ type: 'kfp' });

    cy.openFile('helloworld.pipeline');

    // try to export valid pipeline
    cy.findByRole('button', { name: /export pipeline/i }).click();

    // check label for generic pipeline
    cy.get('.jp-Dialog-header').contains('Export pipeline');

    cy.findByLabelText(/runtime platform/i).select('KUBEFLOW_PIPELINES');

    cy.findByLabelText(/runtime configuration/i)
      .select('kfp_test_runtime')
      .should('have.value', 'kfp_test_runtime');

    // Validate all export options are available
    cy.findByLabelText(/export pipeline as/i)
      .select('KFP static configuration file (YAML formatted)')
      .should('have.value', 'yaml');

    // actual export requires minio
    cy.contains('OK').click();

    // validate job was executed successfully, this can take a while in ci
    cy.findByText(/pipeline export succeeded/i, { timeout: 30000 }).should(
      'be.visible'
    );

    cy.readFile('build/cypress-tests/helloworld.yaml');
  });

  it('should export pipeline as python dsl', () => {
    // Install runtime configuration
    cy.installRuntimeConfig({ type: 'airflow' });

    cy.openFile('helloworld.pipeline');

    // try to export valid pipeline
    cy.findByRole('button', { name: /export pipeline/i }).click();

    // check label for generic pipeline
    cy.get('.jp-Dialog-header').contains('Export pipeline');

    cy.findByLabelText(/runtime platform/i).select('APACHE_AIRFLOW');

    cy.findByLabelText(/runtime configuration/i)
      .select('airflow_test_runtime')
      .should('have.value', 'airflow_test_runtime');

    // overwrite existing helloworld.py file
    cy.findByLabelText(/export pipeline as/i)
      .select('Airflow domain-specific language Python code')
      .should('have.value', 'py');

    cy.findByLabelText(/replace if file already exists/i)
      .check()
      .should('be.checked');

    // actual export requires minio
    cy.contains('OK').click();

    // validate job was executed successfully, this can take a while in ci
    cy.findByText(/pipeline export succeeded/i, { timeout: 30000 }).should(
      'be.visible'
    );

    cy.readFile('build/cypress-tests/helloworld.py');
  });

  it('should not leak properties when switching between nodes', () => {
    cy.openFile('helloworld.pipeline');

    cy.get('#jp-main-dock-panel').within(() => {
      cy.findByText('helloworld.ipynb').rightclick();

      cy.findByRole('menuitem', { name: /properties/i }).click();

      cy.findByText('TEST_ENV_1=1').should('exist');

      cy.findByText('helloworld.py').click();

      cy.get('[data-id="properties-elyra_env_vars"]').within(() => {
        cy.findByRole('button', { name: /add item/i }).click();

        cy.focused().type('BAD=two');

        cy.contains('OK').click();
      });

      cy.findByText('BAD=two').should('exist');

      cy.findByText('helloworld.ipynb').click();

      cy.findByText('TEST_ENV_1=1').should('exist');
      cy.findByText('BAD=two').should('not.exist');

      cy.findByText('helloworld.py').click();

      cy.findByText('BAD=two').should('exist');
    });
  });

  it('kfp pipeline should display custom components', () => {
    cy.createExampleComponentCatalog({ type: 'kfp' });

    cy.createPipeline({ type: 'kfp' });
    cy.get('.palette-flyout-category[value="examples"]').click();

    const kfpCustomComponents = [
      'elyra-kfp-examples-catalog\\:61e6f4141f65', // run notebook using papermill
      'elyra-kfp-examples-catalog\\:737915b826e9', // filter text
      'elyra-kfp-examples-catalog\\:a08014f9252f', // download data
      'elyra-kfp-examples-catalog\\:d68ec7fcdf46' // calculate data hash
    ];

    kfpCustomComponents.forEach(component => {
      cy.get(`#${component}`).should('exist');
    });
  });

  it('kfp pipeline should display expected export options', () => {
    cy.createPipeline({ type: 'kfp' });
    cy.savePipeline();

    cy.installRuntimeConfig({ type: 'kfp' });

    // Validate all export options are available
    cy.findByRole('button', { name: /export pipeline/i }).click();
    cy.findByRole('option', { name: /yaml/i }).should('have.value', 'yaml');
    cy.findByRole('option', { name: /python/i }).should('not.exist');

    // Dismiss dialog
    cy.findByRole('button', { name: /cancel/i }).click();
  });

  it('airflow pipeline should display expected export options', () => {
    cy.createPipeline({ type: 'airflow' });
    cy.savePipeline();

    cy.installRuntimeConfig({ type: 'airflow' });

    // Validate all export options are available
    cy.findByRole('button', { name: /export pipeline/i }).click();
    cy.findByRole('option', { name: /python/i }).should('have.value', 'py');
    cy.findByRole('option', { name: /yaml/i }).should('not.exist');

    // Dismiss dialog
    cy.findByRole('button', { name: /cancel/i }).click();
  });

  it('generic pipeline should display expected export options', () => {
    cy.createPipeline();
    cy.savePipeline();

    // Test Airflow export options
    cy.installRuntimeConfig({ type: 'airflow' });

    cy.findByRole('button', { name: /export pipeline/i }).click();

    // Validate all export options are available for airflow
    cy.findByLabelText(/runtime platform/i).select('APACHE_AIRFLOW');
    cy.findByRole('option', { name: /python/i }).should('have.value', 'py');
    cy.findByRole('option', { name: /yaml/i }).should('not.exist');

    // Dismiss dialog
    cy.findByRole('button', { name: /cancel/i }).click();

    // Test KFP export options
    cy.installRuntimeConfig({ type: 'kfp' });

    cy.findByRole('button', { name: /export pipeline/i }).click();

    // Validate all export options are available for kfp
    cy.findByLabelText(/runtime platform/i).select('KUBEFLOW_PIPELINES');
    cy.findByRole('option', { name: /yaml/i }).should('have.value', 'yaml');
    cy.findByRole('option', { name: /python/i }).should('not.exist');

    // Dismiss dialog
    cy.findByRole('button', { name: /cancel/i }).click();
  });

  it('generic pipeline toolbar should display expected runtime', () => {
    cy.createPipeline();
    cy.get('.toolbar-icon-label').contains(/runtime: generic/i);
  });

  it('kfp pipeline toolbar should display expected runtime', () => {
    cy.createPipeline({ type: 'kfp' });
    cy.get('.toolbar-icon-label').contains(/runtime: kubeflow pipelines/i);
  });

  it('airflow pipeline toolbar should display expected runtime', () => {
    cy.createPipeline({ type: 'airflow' });
    cy.get('.toolbar-icon-label').contains(/runtime: apache airflow/i);
  });
});

// ------------------------------
// ----- Utility Functions
// ------------------------------

const checkEnabledToolbarButtons = (buttons: RegExp[]): void => {
  for (const button of buttons) {
    cy.findByRole('button', { name: button }).should('not.be.disabled');
  }
};

const checkDisabledToolbarButtons = (buttons: RegExp[]): void => {
  for (const button of buttons) {
    cy.findByRole('button', { name: button }).should('be.disabled');
  }
};
