import {IndicatorType, Stakeholder} from "../../../monitor/entities/stakeholder";
import axios from "axios";
import {IndicatorUtils} from "../indicator-utils";
import {Composer} from "../../../utils/email/composer";
import {properties} from "src/environments/environment";


export interface CacheItem {
  reportId: string,
  type: IndicatorType,
  url: string
}

export class Report {
  creator: string;
  name: string;
  success: number;
  errors: {
    url: string,
    status: number
  }[];
  total: number;
  completed: boolean;
  percentage: number
  
  constructor(total: number, name: string, creator: string) {
    this.creator = creator;
    this.name = name;
    this.success = 0;
    this.errors = [];
    this.total = total;
    this.completed = false;
  }
  
  setPercentage() {
    this.percentage = Math.floor((this.success + this.errors.length) / this.total * 100);
  }
}

export class CacheIndicators {
  
  private static BATCH_SIZE = 10;
  
  private reports: Map<string, Report> = new Map<string, Report>();
  private queue: CacheItem[] = [];
  private process: Promise<void>;
  private isFinished: boolean = true;
  
  stakeholderToCacheItems(stakeholder: Stakeholder) {
    let cacheItems: CacheItem[] = [];
    let indicatorUtils = new IndicatorUtils();
    stakeholder.topics.forEach(topic => {
      topic.categories.forEach(category => {
        category.subCategories.forEach(subCategory => {
          subCategory.numbers.forEach(section => {
            section.indicators.forEach(indicator => {
              indicator.indicatorPaths.forEach(indicatorPath => {
                let url = indicatorUtils.getNumberUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath));
                cacheItems.push({
                  reportId: stakeholder._id,
                  type: 'number',
                  url: url
                });
              });
            });
          });
          subCategory.charts.forEach(section => {
            section.indicators.forEach(indicator => {
              indicator.indicatorPaths.forEach(indicatorPath => {
                let url = indicatorUtils.getChartUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath));
                cacheItems.push({
                  reportId: stakeholder._id,
                  type: 'chart',
                  url: url
                });
              });
            });
          });
        });
      });
    });
    return cacheItems;
  }
  
  public exists(id: string) {
    return this.reports.has(id);
  }
  
  public completed(id: string) {
    return !this.exists(id) || this.reports.get(id).completed;
  }
  
  public createReport(id: string, cacheItems: CacheItem[], name: string, creator: string) {
    let report = new Report(cacheItems.length, name, creator);
    this.reports.set(id, report);
    this.addItemsToQueue(cacheItems);
    return report;
  }
  
  public getReport(id: string) {
    return this.reports.get(id);
  }
  
  private async processQueue() {
    this.isFinished = false;
    while (this.queue.length > 0) {
      let batch = this.queue.splice(0, CacheIndicators.BATCH_SIZE);
      await this.processBatch(batch);
    }
  }
  
  private async processBatch(batch: CacheItem[]) {
    let promises: Promise<any>[] = [];
    let ids = new Set<string>();
    batch.forEach(item => {
      let promise;
      ids.add(item.reportId);
      if (item.type === 'chart') {
        let [url, json] = item.url.split('?json=');
        json = decodeURIComponent(json);
        json = statsToolParser(JSON.parse(json));
        promise = axios.post(url, json);
      } else {
        promise = axios.get(item.url);
      }
      promises.push(promise.then(response => {
        let report = this.reports.get(item.reportId);
        if (report) {
          report.success++;
          report.setPercentage();
        }
        return response;
      }).catch(error => {
        let report = this.reports.get(item.reportId);
        if (report) {
          report.errors.push({url: item.url, status: error.response.status});
          report.setPercentage();
        }
        return error.response;
      }));
    });
    await Promise.all(promises);
    ids.forEach(id => {
      let report = this.reports.get(id);
      if (report?.percentage === 100) {
        report.completed = true;
        this.sendEmail(report);
      }
    });
  }
  
  private addItemsToQueue(cacheItems: CacheItem[]) {
    cacheItems.forEach(item => {
      this.queue.push(item);
    });
    if (this.isFinished) {
      this.processQueue().then(() => {
        this.isFinished = true;
      });
    }
  }
  
  sendEmail(report: Report) {
    let email = Composer.composeEmailToReportCachingProcess(report);
    axios.post(properties.adminToolsAPIURL + "sendMail/", email).catch(error => {
      console.error(error);
    });
  }
}

export function statsToolParser(dataJSONobj: any): any {
  let RequestInfoObj = Object.assign({});
  switch (dataJSONobj.library) {
    case "GoogleCharts":
      //Pass the Chart library to ChartDataFormatter
      RequestInfoObj.library = dataJSONobj.library;
      RequestInfoObj.orderBy = dataJSONobj.orderBy;
      
      //Create ChartInfo Object Array
      RequestInfoObj.chartsInfo = [];
      //Create ChartInfo and pass the Chart data queries to ChartDataFormatter
      //along with the requested Chart type
      RequestInfoObj.chartsInfo = dataJSONobj.chartDescription.queriesInfo;
      break;
    case "eCharts":
      //Pass the Chart library to ChartDataFormatter
      RequestInfoObj.library = dataJSONobj.library;
      RequestInfoObj.orderBy = dataJSONobj.orderBy;
      
      //Create ChartInfo Object Array
      RequestInfoObj.chartsInfo = [];
      
      //Create ChartInfo and pass the Chart data queries to ChartDataFormatter
      //along with the requested Chart type
      for (let index = 0; index < dataJSONobj.chartDescription.queries.length; index++) {
        let element = dataJSONobj.chartDescription.queries[index];
        var ChartInfoObj = Object.assign({});
        
        if (element.type === undefined)
          ChartInfoObj.type = dataJSONobj.chartDescription.series[index].type;
        else
          ChartInfoObj.type = element.type;
        
        if (element.name === undefined)
          ChartInfoObj.name = null;
        else
          ChartInfoObj.name = element.name;
        
        ChartInfoObj.query = element.query;
        RequestInfoObj.chartsInfo.push(ChartInfoObj);
      }
      break;
    case "HighCharts":
      RequestInfoObj.library = dataJSONobj.library;
      RequestInfoObj.orderBy = dataJSONobj.orderBy;
      //Pass the Chart type to ChartDataFormatter
      var defaultType = dataJSONobj.chartDescription.chart.type;
      //Create ChartInfo Object Array
      RequestInfoObj.chartsInfo = [];
      //Create ChartInfo and pass the Chart data queries to ChartDataFormatter
      //along with the requested Chart type
      dataJSONobj.chartDescription.queries.forEach(element => {
        var ChartInfoObj = Object.assign({});
        
        if (element.type === undefined)
          ChartInfoObj.type = defaultType;
        else
          ChartInfoObj.type = element.type;
        
        if (element.name === undefined)
          ChartInfoObj.name = null;
        else
          ChartInfoObj.name = element.name;
        
        ChartInfoObj.query = element.query;
        RequestInfoObj.chartsInfo.push(ChartInfoObj);
      });
      break;
    case "HighMaps":
      RequestInfoObj.library = dataJSONobj.library;
      //Create ChartInfo Object Array
      RequestInfoObj.chartsInfo = [];
      
      //Create ChartInfo and pass the Chart data queries to ChartDataFormatter
      dataJSONobj.mapDescription.queries.forEach(element => {
        var ChartInfoObj = Object.assign({});
        
        if (element.name === undefined)
          ChartInfoObj.name = null;
        else
          ChartInfoObj.name = element.name;
        
        ChartInfoObj.query = element.query;
        RequestInfoObj.chartsInfo.push(ChartInfoObj);
      });
      break;
    default:
      console.log("Unsupported Library: " + dataJSONobj.library);
  }
  return RequestInfoObj;
}
