import * as CommonFunctions from '../../common-functions/common-functions';
import * as Models from '../../models/models';
import { FeatureFormatter } from './FeatureFormatter';
import { messages } from 'cucumber-messages';
import { v4 as uuidv4 } from 'uuid';

export class SuiteFactory {
  private userProperties: Models.UserProperties;
  public constructor ( userProperies: Models.UserProperties ) {
    this.userProperties = userProperies;
  }

  /**
   * Parse the json report features
   */
  public async parseJsons ( parsedJsons: Models.Feature[], providedFeatures: messages.IGherkinDocument[] | null ): Promise<Models.ExtendedReport> {
    const suite = this.initializeSuite();
    suite.features = await Promise.all(
      parsedJsons.map( async jsonFeature => {
        const providedFeature = this.getProvidedFeature( providedFeatures, jsonFeature.name )?.feature;
        jsonFeature = await new FeatureFormatter( providedFeature, jsonFeature ).parseFeature( );
        jsonFeature.metadata = this.updateMetadata( jsonFeature.metadata, this.userProperties.metadata );
        suite.results = this.updateSuiteStadistics( suite.results, jsonFeature );
        suite.metadata = this.updateMetadata( suite.metadata, jsonFeature.metadata );
        return jsonFeature;
      } )
    );

    suite.features = suite.features.sort( ( a, b ) => ( a.id > b.id ? 1 : -1 ) );
    suite.results.overview.durationHHMMSS = CommonFunctions.dateTimeFunctions.convertTimeFromNanoSecondsToHHMMSS( suite.results.overview.duration );
    suite.results.overview.date = this.userProperties.date as Date;
    suite.results.overview.executionDate = CommonFunctions.dateTimeFunctions.getDateInDayMonthYear( this.userProperties.date as Date );
    suite.results.overview.executionTime = CommonFunctions.dateTimeFunctions.getTimeInHHMMSS( this.userProperties.date as Date, ':' );

    suite.metadata = this.updateMetadata( suite.metadata, this.userProperties.metadata );
    suite.features = this.setUniqueIdForEachFeature( suite.features );
    if ( this.userProperties.saveEnrichedJSON ) {
      await CommonFunctions.fileSystemFunctions.saveJsonFile( this.userProperties.reportPath, 'enriched-output.json' ,suite );
    }

    return suite;
  }

  private getProvidedFeature( providedFeatures: messages.IGherkinDocument[] | null, jsonFeatureName: string ): messages.IGherkinDocument | null{
    return providedFeatures !== null && providedFeatures?.length > 0
      ? providedFeatures?.filter( document => document.feature?.name === jsonFeatureName )[0]
      : null;
  }

  private updateSuiteStadistics( results: Models.ReportResults, feature: Models.Feature ): Models.ReportResults{
    results.overview.duration += feature.results.overview.duration;
    results.features.passed += feature.results.overview.status.includes( Models.Status.passed ) ? 1 : 0;
    results.features.failed += feature.results.overview.status.includes( Models.Status.failed ) ? 1 : 0;
    results.features.ambiguous += feature.results.overview.status.includes( Models.Status.ambiguous ) ? 1 : 0;
    results.features.total += +1;
    results.scenarios.passed += feature.results.scenarios.passed;
    results.scenarios.failed += feature.results.scenarios.failed;
    results.scenarios.skipped += feature.results.scenarios.skipped;
    results.scenarios.pending += feature.results.scenarios.pending;
    results.scenarios.undefined += feature.results.scenarios.undefined;
    results.scenarios.ambiguous += feature.results.scenarios.ambiguous;
    results.scenarios.total += feature.results.scenarios.total;
    results.steps.passed += feature.results.steps.passed;
    results.steps.failed += feature.results.steps.failed;
    results.steps.skipped += feature.results.steps.skipped;
    results.steps.pending += feature.results.steps.pending;
    results.steps.undefined += feature.results.steps.undefined;
    results.steps.ambiguous += feature.results.steps.ambiguous;
    results.steps.total += feature.results.steps.total;
    return results;
  }

  private updateMetadata( metadataToBeUpdated: Models.Metadata[] | undefined, metadataToBeAdded: Models.Metadata[] | undefined ): Models.Metadata[]{
    if( typeof metadataToBeUpdated !== 'undefined' && typeof metadataToBeAdded !== 'undefined' && metadataToBeAdded?.length > 0 ){
      metadataToBeAdded.forEach( elementToBeAdded => {
        if( metadataToBeUpdated.filter( elementToBeUpdated => elementToBeUpdated.name === elementToBeAdded.name ).length === 0 ){
          metadataToBeUpdated.push( elementToBeAdded );
        }
      } );
      return metadataToBeUpdated;
    }else if( typeof metadataToBeUpdated !== 'undefined' && typeof metadataToBeAdded === 'undefined' || metadataToBeAdded?.length === 0 ){
      return metadataToBeUpdated as Models.Metadata[];
    }else{
      return [] as Models.Metadata[];
    }
  }

  private initializeSuite(): Models.ExtendedReport{
    const suite = {} as Models.ExtendedReport;
    suite.results = Models.reportResultsInitializer();
    suite.metadata = [] as Models.Metadata[];
    return suite;
  }

  private setUniqueIdForEachFeature( features: Models.Feature[] ): Models.Feature[]{
    const seen = new Set();
    features.some( currentObject => {
      if ( seen.size === seen.add( currentObject.id ).size ){
        currentObject.id = currentObject.id + uuidv4();
      }
    } );
    return features;
  }
}
