import prettyMs from 'pretty-ms';
import { equals, head, isNil, last, pipe, prop, T } from 'rambda';
import { asc, byDate, desc } from 'sort-lib';
import { count, incrementBy } from '../../../shared/reducer-utils.js';
import { unique, weeks } from '../../utils.js';
const defaultStat = {
    success: 0,
    failure: 0,
    executionTime: 0,
    total: 0,
};
const getBranchStats = (coverageData) => coverageData[0].coverageStats.find(s => s.label === 'Branch');
const coverageFrom = (coverageData) => {
    if (!(coverageData === null || coverageData === void 0 ? void 0 : coverageData.length))
        return null;
    const branchStats = getBranchStats(coverageData);
    if (!branchStats)
        return null;
    if (branchStats.total === 0)
        return null;
    return { covered: branchStats.covered, total: branchStats.total };
};
const aggregateRuns = (runs) => {
    const testRuns = runs.filter(r => r.startedDate && r.completedDate);
    if (testRuns.length === 0)
        return defaultStat;
    const minStartTime = testRuns.sort(asc(byDate(prop('startedDate'))))[0].startedDate;
    const maxEndTime = testRuns.sort(desc(byDate(prop('completedDate'))))[0].completedDate;
    return {
        ...testRuns.reduce((acc, run) => ({
            success: acc.success + run.passedTests,
            failure: acc.failure + (run.totalTests - run.passedTests),
            total: acc.total + run.totalTests,
        }), { success: 0, failure: 0, total: 0 }),
        executionTime: maxEndTime.getTime() - minStartTime.getTime(),
    };
};
const latestBuilds = (allBuilds) => (inTimeRange) => Object.values(allBuilds).reduce((acc, builds) => {
    const latestBuild = head([...(builds || [])]
        .filter(b => inTimeRange(b.startTime))
        .sort(desc(byDate(prop('startTime')))));
    if (latestBuild)
        acc.push(latestBuild);
    return acc;
}, []);
const extrapolateIfNeeded = async (valuesByWeek, historicalCount, isEmpty, emptyValue) => {
    const historical = await (isEmpty(valuesByWeek[0]) ? historicalCount() : emptyValue);
    return valuesByWeek.reduce((acc, val, index) => {
        if (!isEmpty(val)) {
            acc.push(val);
            return acc;
        }
        // val is empty
        if (index === 0) {
            // No past data to extrapolate from
            acc.push(historical);
            return acc;
        }
        // val is empty, and this isn't the 0th week
        // Extrapolate from previous week
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        acc.push(last(acc));
        return acc;
    }, []);
};
const getMasterBuilds = (allMasterBuilds) => (repoId) => (repoId ? allMasterBuilds[repoId] || {} : []);
const getDefinitionIdsWithoutBuildsInFirstWeek = (allMasterBuilds) => {
    const masterBuilds = getMasterBuilds(allMasterBuilds);
    return unique(Object.keys(allMasterBuilds)
        .filter(repoId => latestBuilds(masterBuilds(repoId))(T).length !== 0)
        .flatMap(repoId => {
        const withoutBuilds = Object.entries(masterBuilds(repoId)).filter(([, builds]) => (builds || []).filter(b => weeks[0](b.startTime)).length === 0);
        return withoutBuilds.map(([definitionId]) => Number(definitionId));
    }));
};
const historicalStatCollector = (allMasterBuilds, getOneBuildBeforeQueryPeriod) => {
    const buildsBeforeQueryPeriod = getOneBuildBeforeQueryPeriod(getDefinitionIdsWithoutBuildsInFirstWeek(allMasterBuilds));
    return (statByBuild, aggregator) => {
        const pBuildsByDefinitionIdBeforeQueryPeriod = buildsBeforeQueryPeriod
            .then(builds => Promise.all(builds.map(async (b) => [b.definition.id, await statByBuild(b)])))
            .then(x => Object.fromEntries(x));
        return async (buildDefinitionId) => {
            const runsByDefinitionId = await pBuildsByDefinitionIdBeforeQueryPeriod;
            const runs = runsByDefinitionId[buildDefinitionId];
            return aggregator(runs);
        };
    };
};
export default (testRunsByBuild, testCoverageByBuildId, getOneBuildBeforeQueryPeriod, allMasterBuilds) => {
    const masterBuilds = getMasterBuilds(allMasterBuilds);
    const collectHistoricalStat = historicalStatCollector(allMasterBuilds, getOneBuildBeforeQueryPeriod);
    const historicalTestCountByBuildId = collectHistoricalStat(testRunsByBuild, runs => runs ? count(incrementBy(prop('totalTests')))(runs) : 0);
    const historicalCoverageByBuildId = collectHistoricalStat(pipe(prop('id'), testCoverageByBuildId), coverage => {
        if (!coverage)
            return null;
        return coverageFrom(coverage.coverageData);
    });
    return async (repoId) => {
        const matchingBuilds = latestBuilds(masterBuilds(repoId))(T);
        if (matchingBuilds.length === 0)
            return null;
        const latestBuildsInEachWeek = weeks.map(latestBuilds(masterBuilds(repoId)));
        const interestingBuilds = [
            ...new Set([...matchingBuilds, ...latestBuildsInEachWeek.flat()]),
        ];
        const [testRunsByBuildIds, testCoverageByBuildIds] = await Promise.all([
            Promise.all(interestingBuilds.map(async (b) => [b.id, await testRunsByBuild(b)])).then(x => Object.fromEntries(x)),
            Promise.all(interestingBuilds.map(async (b) => [b.id, await testCoverageByBuildId(b.id)])).then(x => Object.fromEntries(x)),
        ]);
        const testStats = matchingBuilds.map(build => {
            const runs = testRunsByBuildIds[build.id];
            const coverage = testCoverageByBuildIds[build.id];
            return {
                buildName: build.definition.name,
                buildDefinitionId: build.definition.id,
                url: `${build.url.replace('_apis/build/Builds/', '_build/results?buildId=')}&view=ms.vss-test-web.build-test-results-tab`,
                ...aggregateRuns(runs),
                testsByWeek: latestBuildsInEachWeek.map(builds => builds
                    .flat()
                    .filter(b => b.definition.id === build.definition.id)
                    .map(b => testRunsByBuildIds[b.id])
                    .reduce(incrementBy(count(incrementBy(prop('totalTests')))), 0)),
                coverage: coverageFrom(coverage.coverageData),
                coverageByWeek: latestBuildsInEachWeek.map(builds => builds
                    .flat()
                    .filter(b => b.definition.id === build.definition.id)
                    .map(b => testCoverageByBuildIds[b.id])
                    .reduce((acc, coverage) => {
                    const coverageData = coverageFrom(coverage.coverageData);
                    if (coverageData === null)
                        return acc;
                    if (acc === null)
                        return coverageData;
                    return {
                        covered: acc.covered + coverageData.covered,
                        total: acc.total + coverageData.total,
                    };
                }, null)),
            };
        });
        if (testStats.length === 0)
            return null;
        return Promise.all(testStats
            .filter(stat => stat.success + stat.failure !== 0)
            .map(async (stat) => ({
            name: stat.buildName,
            id: stat.buildDefinitionId,
            url: stat.url,
            successful: stat.success,
            failed: stat.failure,
            executionTime: prettyMs(stat.executionTime),
            testsByWeek: await extrapolateIfNeeded(stat.testsByWeek, () => historicalTestCountByBuildId(stat.buildDefinitionId), equals(0), 0),
            coverageByWeek: stat.coverageByWeek.every(isNil)
                ? null
                : await extrapolateIfNeeded(stat.coverageByWeek, () => historicalCoverageByBuildId(stat.buildDefinitionId), isNil, null),
            coverage: stat.coverage,
        })));
    };
};
