import { ProfilingDataPoint } from '~/testing/profiling/types/ProfilingDataPoint';
import { ProfilingReport } from '~/testing/profiling/types/ProfilingReportByRun';

export const computeProfilingReport = (
  dataPoints: ProfilingDataPoint[],
  varianceThreshold?: number,
) => {
  const profilingReport = { total: {}, runs: {} } as ProfilingReport;

  for (const dataPoint of dataPoints) {
    profilingReport.runs[dataPoint.runName] = {
      ...profilingReport.runs[dataPoint.runName],
      sumById: {
        ...profilingReport.runs[dataPoint.runName]?.sumById,
        [dataPoint.id]:
          (profilingReport.runs[dataPoint.runName]?.sumById?.[dataPoint.id] ??
            0) + dataPoint.durationInMs,
      },
      sum:
        (profilingReport.runs[dataPoint.runName]?.sum ?? 0) +
        dataPoint.durationInMs,
    };
  }

  for (const runName of Object.keys(profilingReport.runs)) {
    const ids = Object.keys(profilingReport.runs[runName].sumById);
    const valuesUnsorted = Object.values(profilingReport.runs[runName].sumById);

    const valuesSortedAsc = [...valuesUnsorted].sort((a, b) => a - b);

    const numberOfIds = ids.length;

    const mean = profilingReport.runs[runName].sum / numberOfIds;

    profilingReport.runs[runName].average = mean;

    profilingReport.runs[runName].min = Math.min(
      ...Object.values(profilingReport.runs[runName].sumById),
    );

    profilingReport.runs[runName].max = Math.max(
      ...Object.values(profilingReport.runs[runName].sumById),
    );

    const intermediaryValuesForVariance = valuesUnsorted.map((value) =>
      Math.pow(value - mean, 2),
    );

    profilingReport.runs[runName].variance =
      intermediaryValuesForVariance.reduce((acc, curr) => acc + curr) /
      numberOfIds;

    const p50Index = Math.floor(numberOfIds * 0.5);
    const p80Index = Math.floor(numberOfIds * 0.8);
    const p90Index = Math.floor(numberOfIds * 0.9);
    const p95Index = Math.floor(numberOfIds * 0.95);
    const p99Index = Math.floor(numberOfIds * 0.99);

    profilingReport.runs[runName].p50 = valuesSortedAsc[p50Index];
    profilingReport.runs[runName].p80 = valuesSortedAsc[p80Index];
    profilingReport.runs[runName].p90 = valuesSortedAsc[p90Index];
    profilingReport.runs[runName].p95 = valuesSortedAsc[p95Index];
    profilingReport.runs[runName].p99 = valuesSortedAsc[p99Index];
  }

  const runNamesForTotal = Object.keys(profilingReport.runs).filter((runName) =>
    runName.startsWith('real-run'),
  );

  const runsForTotal = runNamesForTotal
    .map((runName) => profilingReport.runs[runName])
    .filter((run) => run.variance < (varianceThreshold ?? 0.2));

  profilingReport.total = {
    sum: Object.values(runsForTotal).reduce((acc, run) => acc + run.sum, 0),
    average:
      Object.values(runsForTotal).reduce((acc, run) => acc + run.average, 0) /
      Object.keys(runsForTotal).length,
    min: Math.min(...Object.values(runsForTotal).map((run) => run.min)),
    max: Math.max(...Object.values(runsForTotal).map((run) => run.max)),
    p50:
      Object.values(runsForTotal).reduce((acc, run) => acc + run.p50, 0) /
      Object.keys(runsForTotal).length,
    p80:
      Object.values(runsForTotal).reduce((acc, run) => acc + run.p80, 0) /
      Object.keys(runsForTotal).length,
    p90:
      Object.values(runsForTotal).reduce((acc, run) => acc + run.p90, 0) /
      Object.keys(runsForTotal).length,
    p95:
      Object.values(runsForTotal).reduce((acc, run) => acc + run.p95, 0) /
      Object.keys(runsForTotal).length,
    p99:
      Object.values(runsForTotal).reduce((acc, run) => acc + run.p99, 0) /
      Object.keys(runsForTotal).length,
    dataPointCount: dataPoints.length,
    variance:
      runsForTotal.reduce((acc, run) => acc + run.variance, 0) /
      runsForTotal.length,
  };

  return profilingReport;
};
