import { last, multiply, prop, propEq, range, sort } from 'rambda';
import { asc, byNum, desc } from 'sort-lib';
import { z } from 'zod';
import { BuildDefinitionModel } from './mongoose-models/BuildDefinitionModel.js';
import { RepositoryModel } from './mongoose-models/RepositoryModel.js';
import { getMainBranchBuildIds, queryForFinishTimeInRange, getOneOldCoverageForBuildDefID, getOneOldTestForBuildDefID, getTestsForRepos, getCoveragesForRepos, } from './tests-coverages.js';
import { queryContextInputParser, fromContext } from './utils.js';
import { createIntervals } from '../utils.js';
import { getActiveRepos } from './active-repos.js';
import { divide } from '../../shared/utils.js';
import { getDefinitionListWithRepoInfo } from './build-definitions.js';
import { formatRepoUrlForUI } from './repos.js';
import { buildDefinitionWebUrl } from './builds.js';
export const testRunsForRepositoriesInputParser = z.object({
    queryContext: queryContextInputParser,
    repositoryIds: z.array(z.string()),
});
const getLatest = (weeklyData) => {
    return [...weeklyData].sort(desc(byNum(prop('weekIndex'))))[0];
};
const makeContinuous = async (queryContext, unsortedDataByWeek, getOneOlderItem, emptyValue) => {
    const { startDate, endDate } = fromContext(queryContext);
    const { numberOfIntervals } = createIntervals(startDate, endDate);
    const sortedDataByWeek = unsortedDataByWeek
        ? [...unsortedDataByWeek].sort(byNum(prop('weekIndex')))
        : undefined;
    if (!sortedDataByWeek) {
        const olderTest = await getOneOlderItem(queryContext);
        if (!olderTest)
            return null;
        return range(0, numberOfIntervals).map(weekIndex => ({ ...olderTest, weekIndex }));
    }
    return range(0, numberOfIntervals)
        .reduce(async (acc, weekIndex, index) => {
        const matchingTest = sortedDataByWeek.find(propEq('weekIndex', weekIndex));
        if (matchingTest)
            return [...(await acc), matchingTest];
        if (index === 0) {
            const olderTest = await getOneOlderItem(queryContext);
            return [{ ...(olderTest || emptyValue), weekIndex }];
        }
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const lastItem = last(await acc);
        return [...(await acc), { ...lastItem, weekIndex }];
    }, Promise.resolve([]))
        .then(sort(byNum(prop('weekIndex'))));
};
const mergeBuildDefsAndTests = (buildDefinitions, testsForBuildDefs) => {
    return buildDefinitions.map(definition => ({
        ...definition,
        ...testsForBuildDefs.find(propEq('definitionId', definition.id)),
    }));
};
const mergeBuildDefsAndCoverage = (buildDefsWithTests, branchCoverage) => {
    return buildDefsWithTests.map(definition => {
        var _a;
        return ({
            ...definition,
            coverageByWeek: (_a = branchCoverage.find(propEq('definitionId', definition.id))) === null || _a === void 0 ? void 0 : _a.coverageByWeek,
        });
    });
};
const testsByRepositoryId = async (queryContext, buildDefinitions, testsForBuildDefinitions) => {
    const buildDefsWithTests = mergeBuildDefsAndTests(buildDefinitions, testsForBuildDefinitions);
    const definitionTests = await Promise.all(buildDefsWithTests.map(async (def) => {
        const tests = await makeContinuous(queryContext, def.tests, getOneOldTestForBuildDefID(def.repositoryId, def.id), { hasTests: false });
        return { ...def, latestTest: tests ? getLatest(tests) : null };
    }));
    return definitionTests.reduce((acc, curr) => {
        var _a, _b;
        const matchingRepo = acc.find(propEq('repositoryId', curr.repositoryId));
        if (matchingRepo) {
            matchingRepo.totalTests += ((_a = curr.latestTest) === null || _a === void 0 ? void 0 : _a.hasTests)
                ? curr.latestTest.totalTests
                : 0;
        }
        else {
            acc.push({
                repositoryId: curr.repositoryId,
                totalTests: ((_b = curr.latestTest) === null || _b === void 0 ? void 0 : _b.hasTests) ? curr.latestTest.totalTests : 0,
            });
        }
        return acc;
    }, []);
};
const combineTestsAndCoverageForRepos = async (queryContext, repositoryIds) => {
    const { collectionName, project } = fromContext(queryContext);
    const [buildDefinitions, testsForBuildDefinitions, branchCoverage] = await Promise.all([
        getDefinitionListWithRepoInfo(collectionName, project, repositoryIds),
        getTestsForRepos(queryContext, repositoryIds),
        getCoveragesForRepos(queryContext, repositoryIds),
    ]);
    return mergeBuildDefsAndCoverage(mergeBuildDefsAndTests(buildDefinitions, testsForBuildDefinitions), branchCoverage);
};
export const getTestsAndCoverageForRepoIds = async ({ queryContext, repositoryIds, }) => {
    const testRunsAndCoverageForRepo = await combineTestsAndCoverageForRepos(queryContext, repositoryIds);
    return Promise.all(testRunsAndCoverageForRepo.map(async (def) => {
        const tests = await makeContinuous(queryContext, def.tests, getOneOldTestForBuildDefID(def.repositoryId, def.id), { hasTests: false });
        const coverageData = await makeContinuous(queryContext, def.coverageByWeek, getOneOldCoverageForBuildDefID(def.repositoryId, def.id), { hasCoverage: false });
        const latestTest = tests ? getLatest(tests) : null;
        const url = (latestTest === null || latestTest === void 0 ? void 0 : latestTest.hasTests)
            ? `${def.url.split('_apis')[0]}_build/results?buildId=${latestTest.buildId}&view=ms.vss-test-web.build-test-results-tab`
            : `${def.url.split('_apis')[0]}_build/definition?definitionId=${def.id}`;
        return {
            ...def,
            url,
            tests: tests ? tests.sort(asc(byNum(prop('weekIndex')))) : tests,
            coverageByWeek: coverageData
                ? coverageData.sort(asc(byNum(prop('weekIndex'))))
                : coverageData,
            latestTest,
            latestCoverage: coverageData ? getLatest(coverageData) : null,
        };
    })).then(sort(desc(byNum(x => { var _a; return (((_a = x.latestTest) === null || _a === void 0 ? void 0 : _a.hasTests) ? x.latestTest.totalTests : 0); }))));
};
const getTestsAndCoverageForRepos = async (queryContext, searchTerms, teams) => {
    const activeRepos = await getActiveRepos(queryContext, searchTerms, teams);
    return getTestsAndCoverageForRepoIds({
        queryContext,
        repositoryIds: activeRepos.map(prop('id')),
    });
};
export const getReposListingForTestsDrawer = async ({ queryContext, searchTerms, teams, }) => {
    const testsAndCoverageForRepos = await getTestsAndCoverageForRepos(queryContext, searchTerms, teams);
    return testsAndCoverageForRepos.reduce((acc, curr) => {
        const { repositoryId, latestTest, repositoryName } = curr;
        if (!repositoryId || !repositoryName)
            return acc;
        const repo = acc.find(propEq('repositoryId', repositoryId));
        if (repo) {
            repo.definitions.push(curr);
            repo.totalTests += (latestTest === null || latestTest === void 0 ? void 0 : latestTest.hasTests) ? latestTest.totalTests : 0;
        }
        else {
            acc.push({
                repositoryId,
                repositoryName,
                definitions: [curr],
                totalTests: (latestTest === null || latestTest === void 0 ? void 0 : latestTest.hasTests) ? latestTest.totalTests : 0,
            });
        }
        return acc;
    }, []);
};
export const getTestsAndCoveragePipelinesForDownload = async ({ queryContext, searchTerms, teams, }) => {
    const testsAndCoverageForRepos = await getTestsAndCoverageForRepos(queryContext, searchTerms, teams);
    return testsAndCoverageForRepos.map(x => {
        var _a, _b, _c, _d, _e, _f;
        return ({
            pipelineUrl: buildDefinitionWebUrl(x.url),
            pipelineName: x.name,
            repositoryName: x.repositoryName,
            repositoryUrl: formatRepoUrlForUI(x.repositoryUrl),
            totalTests: ((_a = x.latestTest) === null || _a === void 0 ? void 0 : _a.hasTests) ? x.latestTest.totalTests : 0,
            totalBranches: ((_b = x.latestCoverage) === null || _b === void 0 ? void 0 : _b.hasCoverage)
                ? x.latestCoverage.coverage.totalBranches
                : 0,
            coveredBranches: ((_c = x.latestCoverage) === null || _c === void 0 ? void 0 : _c.hasCoverage)
                ? x.latestCoverage.coverage.coveredBranches
                : 0,
            totalCoverage: ((_d = x.latestCoverage) === null || _d === void 0 ? void 0 : _d.hasCoverage)
                ? divide(x.latestCoverage.coverage.coveredBranches, x.latestCoverage.coverage.totalBranches)
                    .map(multiply(100))
                    .getOr(0)
                : 0,
            failedTests: ((_e = x.latestTest) === null || _e === void 0 ? void 0 : _e.hasTests)
                ? x.latestTest.totalTests - x.latestTest.passedTests
                : 0,
            passedTests: ((_f = x.latestTest) === null || _f === void 0 ? void 0 : _f.hasTests) ? x.latestTest.passedTests : 0,
        });
    });
};
export const getTestsAndCoveragesCount = async (queryContext, repoIds) => {
    var _a, _b, _c, _d;
    const { collectionName, project, startDate, endDate } = fromContext(queryContext);
    const [totalDefs, defsWithTests, defsWithCoverage] = await Promise.all([
        BuildDefinitionModel.find({
            collectionName,
            project,
            repositoryId: { $in: repoIds },
        })
            .countDocuments()
            .exec(),
        RepositoryModel.aggregate([
            ...getMainBranchBuildIds(queryContext, repoIds, queryForFinishTimeInRange(startDate, endDate), false),
            {
                $lookup: {
                    from: 'testruns',
                    let: { buildId: '$build.buildId' },
                    pipeline: [
                        {
                            $match: {
                                collectionName,
                                'project.name': project,
                                '$expr': { $eq: ['$buildConfiguration.id', '$$buildId'] },
                                'release': { $exists: false },
                                // NOTE - This is a workaround to make sure we will fetch the testruns,
                                // where runStatistics array of object field is not empty.
                                // This is happening because Azure itself is not storing the testruns in the database due to some type issue.
                                'runStatistics.state': { $exists: true },
                            },
                        },
                        { $project: { _id: 1 } },
                    ],
                    as: 'tests',
                },
            },
            {
                $project: {
                    repositoryId: '$repositoryId',
                    definitionId: '$build.definitionId',
                    hasTests: { $gt: [{ $size: '$tests' }, 0] },
                },
            },
            { $match: { hasTests: true } },
            {
                $group: {
                    _id: null,
                    defsWithTests: { $addToSet: '$definitionId' },
                    reposWithTests: { $addToSet: '$repositoryId' },
                },
            },
            {
                $project: {
                    _id: 0,
                    count: { $size: '$defsWithTests' },
                    reposCount: { $size: '$reposWithTests' },
                },
            },
        ]),
        RepositoryModel.aggregate([
            ...getMainBranchBuildIds(queryContext, repoIds, queryForFinishTimeInRange(startDate, endDate), false),
            {
                $lookup: {
                    from: 'codecoverages',
                    let: { buildId: '$build.buildId' },
                    pipeline: [
                        {
                            $match: {
                                collectionName,
                                project,
                                '$expr': { $eq: ['$build.id', '$$buildId'] },
                                'coverageData.coverageStats.label': { $in: ['Branch', 'Branches'] },
                            },
                        },
                        { $project: { _id: 1 } },
                    ],
                    as: 'coverage',
                },
            },
            {
                $project: {
                    repositoryId: '$repositoryId',
                    definitionId: '$build.definitionId',
                    hasCoverage: { $gt: [{ $size: '$coverage' }, 0] },
                },
            },
            { $match: { hasCoverage: true } },
            {
                $group: {
                    _id: null,
                    defsWithCoverage: { $addToSet: '$definitionId' },
                    reposWithCoverage: { $addToSet: '$repositoryId' },
                },
            },
            {
                $project: {
                    _id: 0,
                    count: { $size: '$defsWithCoverage' },
                    reposCount: { $size: '$reposWithCoverage' },
                },
            },
        ]),
    ]);
    return {
        totalDefs,
        defsWithTests: ((_a = defsWithTests[0]) === null || _a === void 0 ? void 0 : _a.count) || 0,
        defsWithCoverage: ((_b = defsWithCoverage[0]) === null || _b === void 0 ? void 0 : _b.count) || 0,
        reposWithTests: ((_c = defsWithTests[0]) === null || _c === void 0 ? void 0 : _c.reposCount) || 0,
        reposWithCoverage: ((_d = defsWithCoverage[0]) === null || _d === void 0 ? void 0 : _d.reposCount) || 0,
    };
};
const mergeNestedForWeekIndex = (list, innerList, combiner, emptyValue) => {
    const byWeekIndex = list.reduce((acc, item) => {
        const nestedList = innerList(item);
        if (!nestedList)
            return acc;
        return nestedList.reduce((acc, innerItem) => {
            acc.set(innerItem.weekIndex, combiner(acc.get(innerItem.weekIndex) || emptyValue, innerItem));
            return acc;
        }, acc);
    }, new Map());
    return [...byWeekIndex.entries()]
        .map(([weekIndex, testCounts]) => ({ weekIndex, ...testCounts }))
        .sort(asc(byNum(prop('weekIndex'))));
};
export const getTestsAndCoverageByWeek = async (queryContext, repositoryIds) => {
    const testsAndCoverage = await getTestsAndCoverageForRepoIds({
        queryContext,
        repositoryIds,
    });
    const testsByWeek = mergeNestedForWeekIndex(testsAndCoverage, prop('tests'), (prev, definition) => ({
        hasTests: prev.hasTests || definition.hasTests,
        passedTests: (prev.hasTests ? prev.passedTests : 0) +
            (definition.hasTests ? definition.passedTests : 0),
        totalTests: (prev.hasTests ? prev.totalTests : 0) +
            (definition.hasTests ? definition.totalTests : 0),
    }), { hasTests: false });
    const coveragesByWeek = mergeNestedForWeekIndex(testsAndCoverage, prop('coverageByWeek'), (prev, definition) => ({
        hasCoverage: prev.hasCoverage || definition.hasCoverage,
        coveredBranches: (prev.hasCoverage ? prev.coveredBranches : 0) +
            (definition.hasCoverage ? definition.coverage.coveredBranches : 0),
        totalBranches: (prev.hasCoverage ? prev.totalBranches : 0) +
            (definition.hasCoverage ? definition.coverage.totalBranches : 0),
    }), { hasCoverage: false });
    return { testsByWeek, coveragesByWeek };
};
export const getTotalTestsForRepositoryIds = async (queryContext, repositoryIds) => {
    const { collectionName, project } = fromContext(queryContext);
    const [testsFromDefsOfRepoIds, definitionList] = await Promise.all([
        getTestsForRepos(queryContext, repositoryIds),
        getDefinitionListWithRepoInfo(collectionName, project, repositoryIds),
    ]);
    return testsByRepositoryId(queryContext, definitionList, testsFromDefsOfRepoIds);
};
export const getReposSortedByTests = async (queryContext, repositoryIds, sortOrder, pageSize, pageNumber) => {
    const allRepos = await getTotalTestsForRepositoryIds(queryContext, repositoryIds).then(sort((sortOrder === 'asc' ? asc : desc)(byNum(repo => repo.totalTests))));
    return allRepos.slice(pageNumber * pageSize, (pageNumber + 1) * pageSize);
};
