import * as CommonFunctions from '../../common-functions/common-functions';
import * as Models from '../../models/models';
import * as lodash from 'lodash';
import { ScenarioFormatter } from './ScenarioFormatter';
import { getIcon } from '../helpers/FeatureIcons';
import { messages } from 'cucumber-messages';

export class FeatureFormatter {
  private gherkinFeature: messages.GherkinDocument.IFeature | null | undefined ;
  private jsonFeature: Models.Feature;
  private scenarioFormater: ScenarioFormatter;

  public constructor ( gherkinFeature: messages.GherkinDocument.IFeature | null | undefined, jsonFeature: Models.Feature ) {
    this.gherkinFeature = gherkinFeature;
    this.jsonFeature = jsonFeature;
    this.scenarioFormater = new ScenarioFormatter( this.gherkinFeature );
  }

  /**
   * Parse the scenarios of the feature
   */
  public async parseFeature ( ): Promise<Models.Feature> {
    this.jsonFeature = this.initalizeFeature( this.jsonFeature );

    // parse features
    if( typeof this.jsonFeature?.elements !== 'undefined' && ( this.jsonFeature?.elements as Models.Scenario[] )?.length > 0 ){
      {this.jsonFeature.elements = await Promise.all(
        ( this.jsonFeature.elements as Models.Scenario[] ).map( jsonScenario => {
          jsonScenario = this.scenarioFormater.parseScenario( jsonScenario, this.jsonFeature.name );
          this.jsonFeature.results = this.updateFeatureStadistics( this.jsonFeature?.results, jsonScenario?.results );
          return jsonScenario;
        } )
      );}

      this.jsonFeature.results.overview.durationHHMMSS =
      CommonFunctions.dateTimeFunctions.convertTimeFromNanoSecondsToHHMMSS( this.jsonFeature.results.overview.duration );
      this.joinOutlineScenarios();

      this.jsonFeature.elements = this.jsonFeature.elements.filter( scenario => scenario != null && Object.keys( scenario ).length !== 0 );

      // add first scenario outline
      this.jsonFeature.elements = this.createFirstScenariosOutline( );
    }

    if( ( this.jsonFeature?.metadata )?.length > 0 ){
      ( this.jsonFeature.metadata ).map( metadataElement => metadataElement.icon = getIcon( metadataElement.value ) );
    }

    return this.jsonFeature;
  }

  private createFirstScenariosOutline( ): Models.Scenario[]{
    const featureScenarios = lodash.cloneDeep( this.jsonFeature.elements );
    let insertedElements = -1;
    ( featureScenarios as Models.Scenario[] ).forEach( ( jsonScenario, index ) => {
      if( this.isFirstOutlineScenario( jsonScenario, index ) ){
      // if( jsonScenario?.id?.substring( jsonScenario.id.length - 3, jsonScenario.id.length ) === ';;2' && typeof this.gherkinFeature !== 'undefined' ){
        insertedElements += 1;
        const scenarioIndex = ( featureScenarios as Models.Scenario[] ).indexOf( jsonScenario );
        const providedScenario = ( this.gherkinFeature?.children as messages.GherkinDocument.Feature.IFeatureChild[] ).filter(
          scenario => scenario?.scenario?.name === jsonScenario.name )[0]?.scenario;
        const firstScenarioOutline = this.scenarioFormater.createNewFirstScenarioOutline( jsonScenario, providedScenario, this.jsonFeature, scenarioIndex );
        // if( firstScenarioOutline !== null ){
        ( this.jsonFeature.elements as Models.Scenario[] ).splice( scenarioIndex + insertedElements, 0, firstScenarioOutline );
        // }
      }
    } );
    return this.jsonFeature.elements as Models.Scenario[];
  }

  private getFeatureStatus( featureResults: Models.FeatureResults ): string[]{
    const result=[] as Models.Status[];

    if( featureResults.scenarios.ambiguous > 0 ){
      result.push( Models.Status.ambiguous );
    }

    if( featureResults.scenarios.pending > 0 ){
      result.push( Models.Status.pending );
    }

    if ( featureResults.scenarios.undefined > 0 ){
      result.push( Models.Status.undefined );
    }

    if ( featureResults.scenarios.skipped > 0 ){
      result.push( Models.Status.skipped );
    }

    if ( featureResults.scenarios.failed > 0 ){
      result.push( Models.Status.failed );
    }

    if ( featureResults.scenarios.passed > 0 ){
      result.push( Models.Status.passed );
    }

    return result;
  }

  private getFeatureIcon( featureResults: Models.FeatureResults ): string[]{
    const result=[] as string[];

    if( featureResults.scenarios.ambiguous > 0 ){
      result.push( getIcon( Models.Status.ambiguous.toString() ) );
    }

    if( featureResults.scenarios.pending > 0 ){
      result.push( getIcon( Models.Status.pending.toString() ) );
    }

    if ( featureResults.scenarios.undefined > 0 ){
      result.push( getIcon( Models.Status.undefined.toString() ) );
    }

    if ( featureResults.scenarios.skipped > 0 ){
      result.push( getIcon( Models.Status.skipped.toString() ) );
    }

    if ( featureResults.scenarios.failed > 0 ){
      result.push( getIcon( Models.Status.failed.toString() ) );
    }

    if ( featureResults.scenarios.passed > 0 ){
      result.push( getIcon( Models.Status.passed.toString() ) );
    }

    return result;
  }

  private updateFeatureStadistics( featureResults: Models.FeatureResults, scenarioResults: Models.ScenarioResults ): Models.FeatureResults{
    if( typeof scenarioResults === 'undefined' ){
      return featureResults;
    }

    // update feature scenarios stadistics
    featureResults.scenarios.failed += scenarioResults.steps.failed > 0 ? 1 : 0;
    featureResults.scenarios.ambiguous += scenarioResults.steps.ambiguous > 0 ? 1 : 0;
    featureResults.scenarios.undefined += scenarioResults.steps.undefined > 0 ? 1 : 0;
    featureResults.scenarios.pending += scenarioResults.steps.pending > 0 ? 1 : 0;
    featureResults.scenarios.skipped += scenarioResults.steps.skipped > 0 ? 1 : 0;
    featureResults.scenarios.passed +=
                  scenarioResults.steps.passed > 0 &&
                  scenarioResults.steps.failed === 0 &&
                  scenarioResults.steps.skipped === 0 &&
                  scenarioResults.steps.pending === 0 &&
                  scenarioResults.steps.undefined === 0 ? 1 : 0;
    featureResults.scenarios.total =
                  featureResults.scenarios.passed +
                  featureResults.scenarios.skipped +
                  featureResults.scenarios.pending +
                  featureResults.scenarios.undefined +
                  featureResults.scenarios.ambiguous +
                  featureResults.scenarios.failed;

    // update feature steps stadistics
    featureResults.steps.passed += scenarioResults.steps.passed;
    featureResults.steps.failed += scenarioResults.steps.failed;
    featureResults.steps.skipped += scenarioResults.steps.skipped;
    featureResults.steps.pending += scenarioResults.steps.pending;
    featureResults.steps.undefined += scenarioResults.steps.undefined;
    featureResults.steps.ambiguous += scenarioResults.steps.ambiguous;
    featureResults.steps.total += scenarioResults.steps.total;

    // update feature result
    featureResults.overview.status = this.getFeatureStatus( featureResults );
    featureResults.overview.duration += scenarioResults.overview.duration;
    featureResults.overview.statusIcons = this.getFeatureIcon( featureResults );

    return featureResults;
  }

  private initalizeFeature( jsonFeature: Models.Feature ): Models.Feature{
    jsonFeature.description = jsonFeature.description === null || typeof jsonFeature.description === 'undefined' ? '' : jsonFeature.description;
    jsonFeature.elements = jsonFeature.elements === null || typeof jsonFeature.elements === 'undefined' ? [] : jsonFeature.elements;
    jsonFeature.results = Models.featureResultsInitializer();
    jsonFeature.tags = jsonFeature.tags == null || typeof jsonFeature.tags == 'undefined' ? [] : jsonFeature.tags;
    jsonFeature.metadata = ( jsonFeature?.metadata )?.length > 0 ? jsonFeature.metadata : [];
    return jsonFeature;
  }

  private joinOutlineScenarios( ): void{
    const scenarioOutlines: {
      index: number;
      name: string;
    }[] = [];
    this.jsonFeature?.elements?.forEach( scenario => {
      if ( this.scenarioFormater.isOutlineScenario( scenario ) ){
        scenarioOutlines.push( { index: parseInt( `${( this.jsonFeature?.elements as Models.Scenario[] ).indexOf( scenario )}`, 10 ),
                                 name: `${scenario.name}` } );
      }
    } );
    const scenarioOutlineNames = [...new Set( scenarioOutlines.map( item => item.name ) )];
    scenarioOutlineNames.forEach( name =>{
      const scenarioOutlineElements = scenarioOutlines.filter( scenario => scenario.name === name );
      const firstScenarioIndex = scenarioOutlineElements[0].index;
      scenarioOutlineElements.forEach( ( scenarioOutlineElement, index ) =>{
        CommonFunctions.arrayFunctions.arraymove( this.jsonFeature?.elements as Models.Scenario[],scenarioOutlineElement.index, firstScenarioIndex + index );
        // ( jsonFeature.elements as Models.Scenario[] ).splice( scenarioIndex + insertedELements, 0, firstScenarioOutline );
      } );
    } );
  }

  private isFirstOutlineScenario( scenario: Models.Scenario, index: number ): boolean{
    if ( this.getOutlineScenarios().filter( scenarioElement => scenarioElement.name === scenario.name )[0]?.index === index ){
      return true;
    }
    return false;
  }

  private getOutlineScenarios(): ScenarioOutlines[]{
    const scenarioOutlines: ScenarioOutlines[] = [];
    this.jsonFeature?.elements?.forEach( scenario => {
      if ( this.scenarioFormater.isOutlineScenario( scenario ) ){
        scenarioOutlines.push( { index: parseInt( `${( this.jsonFeature?.elements as Models.Scenario[] ).indexOf( scenario )}`, 10 ),
                                 name: `${scenario.name}` } );
      }
    } );
    return scenarioOutlines;
  }
}

interface ScenarioOutlines{
  index: number;
  name: string;
}
