import * as Models from '../../models/models';
import * as fse from 'fs-extra';
import * as lodash from 'lodash';
import * as path from 'path';
import * as scripts from './HtmlChartsScriptsFunctions';

import { Messages } from './ConsoleMessages';
import chalk from 'chalk';
import open from 'open';
const hmlFunctions = scripts.HtmlScriptsFunctions;

const featureOverviewScriptsTemplate = 'templates/scripts/featureOverviewScripts.tmpl';
const featuresOverviewScriptsTemplate = 'templates/scripts/featuresOverviewScripts.tmpl';
const reportStylesheetTemplate = 'templates/style.css';
const featuresOverviewIndexTemplate = 'templates/components/features-overview/features-overview-index.tmpl';
const featuresOverviewTableTemplate = 'templates/components/features-overview/features-overview-table.tmpl';
const featuresChartsTemplate = 'templates/components/features-overview/features-charts.tmpl';
const featureChartsTemplate = 'templates/components/feature-overview/feature-charts.tmpl';
const featureOverviewIndexTemplate = 'templates/components/feature-overview/feature-overview.index.tmpl';
const scenarioStepsTemplate = 'templates/components/feature-overview/scenario-elements/steps.tmpl';
const scenarioBeforeTemplate = 'templates/components/feature-overview/scenario-elements/before.tmpl';
const scenarioAfterTemplate = 'templates/components/feature-overview/scenario-elements/after.tmpl';
const tagsTemplate = 'templates/components/feature-overview/scenario-elements/tags.tmpl';
const scenarioNameAndResultsTemplate = 'templates/components/feature-overview/scenario-elements/name-and-results.tmpl';
const scenarioResultsTemplate = 'templates/components/feature-overview/scenario-elements/results.tmpl';
const simpleScenarioTemplate = 'templates/components/feature-overview/scenarios/simple-scenario.tmpl';
const outlineScenarioTemplate = 'templates/components/feature-overview/scenarios/outline-scenario.tmpl';

enum ScenarioElements{
  Tags = 'Tags',
  NameAndResults = 'NameAndResults',
  Before = 'Before',
  After = 'After',
  Steps = 'Steps',
  Results = 'Results'
}

export class GenerateHtml {
  private suite: Models.ExtendedReport;
  private scriptsFunctions: scripts.HtmlScriptsFunctions;
  private userProperties: Models.UserProperties;
  private featuresOverviewIndex: string;

  public constructor ( suite: Models.ExtendedReport, userPproperties: Models.UserProperties ) {
    this.suite = suite;
    this.scriptsFunctions = new hmlFunctions( this.suite );
    this.userProperties = userPproperties;
    this.featuresOverviewIndex = path.join( this.userProperties.reportPath as string, 'index.html' );
    // lodash.templateSettings.interpolate = /\{\{=(.+?)\}\}/g;
    // lodash.templateSettings.evaluate = /\{\{(.+?)\}\}/g;
  }

  public async createHtmlPages(): Promise<void> {
    this._createFeaturesOverviewIndexPage();
    await this._createFeatureIndexPages();
    await this.openReportInBrowser();
  }

  public async openReportInBrowser (): Promise<void> {
    /* istanbul ignore else */
    if ( !this.userProperties.disableLog ) {
      console.log(
        chalk.blue(
          Messages.reportCreatedBegin + this.featuresOverviewIndex + Messages.reportCreatedEnd
        )
      );
    }

    if ( this.userProperties.openReportInBrowser ) {
      const dir = path.join( this.userProperties.reportPath as string, 'features' );
      const files = fse.readdirSync( dir );

      /* istanbul ignore next */
      if ( files.length === 1 && this.userProperties.navigateToFeatureIfThereIsOnlyOne ) {
        await open( path.join( dir, files[0] ) );
      } else {
        await open( this.featuresOverviewIndex );
      }
    }
  }

  /**
   * Generate the features overview
   *
   * @private
   */
  private _createFeaturesOverviewIndexPage (): void {
    fse.writeFileSync(
      this.featuresOverviewIndex,
      this.generateTemplate( featuresOverviewIndexTemplate,{
        featuresOverview: this.generateTemplate( featuresOverviewTableTemplate, {
          showExecutionTime: this.userProperties.showExecutionTime ,
          suite: this.suite
        } ),
        featuresOverviewChart: this.generateTemplate( featuresChartsTemplate,{
          config: this.userProperties,
          suite: this.suite
        } ),
        featuresOverviewScripts: this.generateTemplate( featuresOverviewScriptsTemplate, {
          config: this.userProperties,
          scriptsFunctions: this.scriptsFunctions,
          suite: this.suite
        } ),
        projectName: this.userProperties.projectName,
        styles: this.generateTemplate( reportStylesheetTemplate ),
        suite: this.suite
      } )
    );
  }

  /**
   * Generate the feature pages
   *
   * @private
   */
  private async _createFeatureIndexPages (): Promise<void> {
    const featuresPath = path.resolve( this.userProperties.reportPath as string,'features/' );
    if( ! await fse.pathExists( featuresPath ) ){
      fse.mkdirSync( featuresPath );
    }

    ( this.suite.features ).forEach( feature => {
      const featurePage = `${featuresPath}/${feature.id.toString().replace( '/', '_' )}.html`;
      fse.writeFileSync(
        featurePage,
        this.generateTemplate( featureOverviewIndexTemplate, {
          feature,
          featureOverviewScripts: this.generateTemplate( featureOverviewScriptsTemplate, {
            feature,
            scriptsFunctions: this.scriptsFunctions,
            suite: this.suite
          } ),
          featureScenariosOverviewChart: this.generateTemplate( featureChartsTemplate,{
            featureMetadata: feature.metadata
          } ),
          scenarioOutlineTemplate: this.generateScenariosTemplate( feature ),
          styles: this.generateTemplate( reportStylesheetTemplate ) ,
          suite: this.suite,
          tags: this.generateTemplate( tagsTemplate,{
            index: feature.id,
            tags: feature?.tags
          } ),
        } )
      );
    } );
  }

  private generateScenariosTemplate ( feature: Models.Feature ): string {
    const scenarioTemplates = this.generateScenarioElements( feature );
    let scenariosTemplates = '';
    const featureElementsLength = feature?.elements?.length as number;
    for ( let scenarioIndex = 0; scenarioIndex < featureElementsLength; scenarioIndex++ ) {
      const scenario = ( feature.elements as Models.Scenario[] )[scenarioIndex];
      const scenarioId = scenario?.id;
      const simpleScenario = this.generateTemplate( simpleScenarioTemplate, {
        isFirstScenarioOutline: scenario.isFirstScenarioOutline,
        scenarioId,
        scenarioIndex,
        scenarioOutline: this.generateTemplate( outlineScenarioTemplate, {
          config: this.userProperties,
          isFirstScenarioOutline: scenario.isFirstScenarioOutline,
          scenario,
          scenarioId: scenario?.id,
          scenarioIndex,
          scenarioTemplates
        } ),
        scenarioTemplates
      } );

      scenariosTemplates = scenariosTemplates + simpleScenario;
      const scenarioExampleslength = scenario?.examples?.length as number;
      scenarioIndex += scenarioExampleslength > 0 ? scenarioExampleslength - 1 : 0;
    }
    return scenariosTemplates;
  }

  private generateScenarioElements ( feature: Models.Feature ): Map<string, any> {
    const scenarioTemplates = new Map();
    const featureElementsLength = feature?.elements?.length as number;
    for ( let scenarioIndex = 0; scenarioIndex < featureElementsLength; scenarioIndex++ ) {
      const scenario = ( feature.elements as Models.Scenario[] )[scenarioIndex];
      const scenarioElementsTemplates = new Map();

      const scenarioTags = this.generateTemplate( tagsTemplate, {
        index: scenario?.id,
        tags: scenario?.tags
      } );
      scenarioElementsTemplates.set( ScenarioElements.Tags, scenarioTags );

      const scenarioNameAndResults = this.generateTemplate( scenarioNameAndResultsTemplate,{
        config: this.userProperties,
        scenario,
        scenarioIndex
      } );

      scenarioElementsTemplates.set( ScenarioElements.NameAndResults, scenarioNameAndResults );

      const scenarioResults = this.generateTemplate( scenarioResultsTemplate,{
        scenario,
        scenarioIndex
      } );
      scenarioElementsTemplates.set( ScenarioElements.Results, scenarioResults );

      const scenarioSteps = this.generateTemplate( scenarioStepsTemplate, {
        config: this.userProperties,
        isFirstScenarioOutline: scenario.isFirstScenarioOutline,
        scenarioIndex,
        steps: scenario.steps
      } );
      scenarioElementsTemplates.set( ScenarioElements.Steps, scenarioSteps );

      const scenarioBefore = this.generateTemplate( scenarioBeforeTemplate, {
        config: this.userProperties,
        scenario,
        scenarioIndex,
        steps: this.generateTemplate( scenarioStepsTemplate, {
          config: this.userProperties,
          isFirstScenarioOutline: scenario.isFirstScenarioOutline,
          scenarioIndex,
          steps: scenario?.before?.steps
        } )
      } );
      scenarioElementsTemplates.set( ScenarioElements.Before, scenarioBefore );

      const scenarioAfter = this.generateTemplate( scenarioAfterTemplate, {
        config: this.userProperties,
        scenario,
        scenarioIndex,
        steps: this.generateTemplate( scenarioStepsTemplate, {
          config: this.userProperties,
          isFirstScenarioOutline: scenario.isFirstScenarioOutline,
          scenarioIndex,
          steps: scenario.after?.steps
        } )
      } );
      scenarioElementsTemplates.set( ScenarioElements.After, scenarioAfter );

      scenarioTemplates.set( scenarioIndex, scenarioElementsTemplates );
    }
    return scenarioTemplates as Map<string, any>;
  }

  /**
   * Read a template file and return it's content
   *
   * @param {string} fileName
   * @return {*} Content of the file
   * @private
   */
  private readTemplateFile ( fileName: string ): string {
    return fse.readFileSync( path.join( __dirname, '../', fileName ), 'utf-8' );
  }

  private generateTemplate( templateName: string, parameters?: Record<string, any> ): string{
    // parameters = [parameters, {"interpolate": /<?=([\s\S]+?)%>/g) ?>}];

    const compiledTemplate = lodash.template( this.readTemplateFile( templateName ) );
    return compiledTemplate( parameters );
  }
}
