import { asc, byNum, byString } from 'sort-lib';
import { difference, groupBy, intersection, prop, propEq, range, union } from 'rambda';
import path from 'node:path';
import md5 from 'md5';
import { inDateRange } from './helpers.js';
import { fromContext as fromAzureContext, fromGithubContext, weekIndexValue, } from './utils.js';
import { createIntervals } from '../utils.js';
import { GithubBuildReportModel, AzureBuildReportModel } from './build-reports/index.js';
const isAzureContext = (x) => x.length === 4;
const getModel = (x) => isAzureContext(x) ? AzureBuildReportModel : GithubBuildReportModel;
const precondition = (x) => {
    if (isAzureContext(x)) {
        const { collectionName, project } = fromAzureContext(x);
        return { collectionName, project };
    }
    const { orgId } = fromGithubContext(x);
    return { orgId };
};
const getBuildDefIds = async (Model, queryContext) => {
    const groupAndProjectIds = [
        { $group: { _id: '$buildDefinitionId' } },
        { $project: { _id: 0, id: '$_id' } },
    ];
    return Model.aggregate([{ $match: precondition(queryContext) }, ...groupAndProjectIds]);
};
const buildReportsWithSpecmatic = (queryContext, additionalClauses = {}) => ({
    $match: {
        ...precondition(queryContext),
        specmaticConfigPath: { $exists: true },
        ...additionalClauses,
    },
});
const addCoveredOperationsFields = {
    $addFields: {
        coveredOperations: {
            $size: {
                $filter: {
                    input: '$specmaticCoverage.operations',
                    as: 'operation',
                    cond: { $eq: ['$$operation.coverageStatus', 'covered'] },
                },
            },
        },
    },
};
const groupByBuildId = {
    $group: {
        _id: '$buildId',
        buildDefinitionId: { $first: '$buildDefinitionId' },
        coveredOperations: { $sum: '$coveredOperations' },
        createdAt: { $first: '$createdAt' },
    },
};
const getQueryDates = (queryContext) => {
    const { startDate, endDate } = isAzureContext(queryContext)
        ? fromAzureContext(queryContext)
        : fromGithubContext(queryContext);
    return { startDate, endDate };
};
const getWeeklyApiCoveragePercentage = async (queryContext) => {
    const Model = getModel(queryContext);
    const { startDate, endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            createdAt: inDateRange(startDate, endDate),
            specmaticCoverage: { $exists: true },
        }),
        { $unwind: '$specmaticCoverage' },
        { $match: { 'specmaticCoverage.serviceType': 'HTTP' } },
        addCoveredOperationsFields,
        groupByBuildId,
        { $sort: { createdAt: 1 } },
        {
            $group: {
                _id: {
                    weekIndex: weekIndexValue(startDate, '$createdAt'),
                    buildDefinitionId: '$buildDefinitionId',
                },
                coveredOperations: { $last: '$coveredOperations' },
                // for debugging
                latestBuildId: { $last: '$_id' },
            },
        },
        {
            $project: {
                _id: 0,
                weekIndex: '$_id.weekIndex',
                buildDefinitionId: '$_id.buildDefinitionId',
                coveredOperations: 1,
                // for debugging
                latestBuildId: 1,
            },
        },
    ]);
};
export const getOneOlderApiCoverageForBuildDefinition = async (queryContext, buildDefinitionId) => {
    const Model = getModel(queryContext);
    const { startDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            buildDefinitionId,
            createdAt: { $lt: startDate },
            specmaticCoverage: { $exists: true },
        }),
        { $sort: { createdAt: -1 } },
        { $limit: 1 },
        { $unwind: '$specmaticCoverage' },
        { $match: { 'specmaticCoverage.serviceType': 'HTTP' } },
        addCoveredOperationsFields,
        groupByBuildId,
        { $addFields: { buildId: '$_id' } },
        { $project: { _id: 0 } },
    ]).then(results => (results.length ? results[0] : null));
};
const makeContinuous = async (queryContext, weeklyData, getOneOlderItem, makeNonNull) => {
    const { startDate, endDate } = getQueryDates(queryContext);
    const { numberOfIntervals } = createIntervals(startDate, endDate);
    const hasDataForTheFirstWeek = weeklyData.some(propEq('weekIndex', 0));
    const dataByWeek = hasDataForTheFirstWeek
        ? weeklyData
        : [{ weekIndex: 0, ...makeNonNull(await getOneOlderItem()) }, ...weeklyData];
    return range(0, numberOfIntervals).reduce((acc, weekIndex) => {
        const matchingItem = dataByWeek.find(propEq('weekIndex', weekIndex));
        if (matchingItem) {
            acc.push(matchingItem);
            return acc;
        }
        const lastItem = acc.at(-1);
        acc.push({ weekIndex, ...makeNonNull(lastItem) });
        return acc;
    }, []);
};
export const getWeeklyApiCoverageSummary = async (queryContext) => {
    const Model = getModel(queryContext);
    const { startDate, endDate } = getQueryDates(queryContext);
    const { numberOfIntervals } = createIntervals(startDate, endDate);
    const [apiCoveragesForDefs, buildDefs] = await Promise.all([
        getWeeklyApiCoveragePercentage(queryContext),
        getBuildDefIds(Model, queryContext),
    ]);
    const buildDefsWithCoverage = buildDefs.map(buildDef => {
        const matchingCoverages = apiCoveragesForDefs.filter(coverage => coverage.buildDefinitionId === buildDef.id.toString());
        return {
            buildDefId: buildDef.id.toString(),
            coverageByWeek: matchingCoverages.sort(asc(byNum(w => w.weekIndex))),
        };
    });
    const continuousCoverage = await Promise.all(buildDefsWithCoverage.map(async ({ buildDefId, coverageByWeek }) => {
        return {
            buildDefId,
            coverageByWeek: await makeContinuous(queryContext, coverageByWeek, () => getOneOlderApiCoverageForBuildDefinition(queryContext, buildDefId), item => ({
                buildDefinitionId: buildDefId,
                coveredOperations: (item === null || item === void 0 ? void 0 : item.coveredOperations) || 0,
            })),
        };
    }));
    return range(0, numberOfIntervals).map(weekIndex => {
        const weekCoverage = continuousCoverage.reduce((acc, { coverageByWeek }) => {
            const matchingCoverage = coverageByWeek.find(coverage => coverage.weekIndex === weekIndex);
            if (matchingCoverage) {
                acc.coveredOperations += matchingCoverage.coveredOperations;
            }
            return acc;
        }, { coveredOperations: 0 });
        return {
            weekIndex,
            coveredOperations: weekCoverage.coveredOperations,
        };
    });
};
const addFieldsStubUsage = [
    { $unwind: '$specmaticStubUsage' },
    { $match: { 'specmaticStubUsage.serviceType': 'HTTP' } },
    {
        $addFields: {
            zeroCountOperations: {
                $size: {
                    $filter: {
                        input: '$specmaticStubUsage.operations',
                        as: 'operation',
                        cond: { $eq: ['$$operation.count', 0] },
                    },
                },
            },
            usedOperations: {
                $size: {
                    $filter: {
                        input: '$specmaticStubUsage.operations',
                        as: 'operation',
                        cond: { $ne: ['$$operation.count', 0] },
                    },
                },
            },
        },
    },
];
const getStubUsage = async (queryContext) => {
    const Model = getModel(queryContext);
    const { startDate, endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            createdAt: inDateRange(startDate, endDate),
            specmaticStubUsage: { $exists: true },
        }),
        ...addFieldsStubUsage,
        {
            $group: {
                _id: '$buildId',
                buildDefinitionId: { $first: '$buildDefinitionId' },
                zeroCountOperations: { $sum: '$zeroCountOperations' },
                usedOperations: { $sum: '$usedOperations' },
                createdAt: { $first: '$createdAt' },
            },
        },
        { $sort: { createdAt: 1 } },
        {
            $group: {
                _id: {
                    weekIndex: weekIndexValue(startDate, '$createdAt'),
                    buildDefinitionId: '$buildDefinitionId',
                },
                zeroCountOperations: { $last: '$zeroCountOperations' },
                usedOperations: { $last: '$usedOperations' },
                latestBuildId: { $last: '$_id' },
            },
        },
        {
            $addFields: {
                weekIndex: '$_id.weekIndex',
                buildDefinitionId: '$_id.buildDefinitionId',
            },
        },
        { $project: { _id: 0 } },
    ]);
};
const getOneOlderStubUsageForBuildDefinition = async (queryContext, buildDefinitionId) => {
    const Model = getModel(queryContext);
    const { startDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            buildDefinitionId,
            createdAt: { $lt: startDate },
            specmaticStubUsage: { $exists: true },
        }),
        { $sort: { createdAt: -1 } },
        { $limit: 1 },
        ...addFieldsStubUsage,
        {
            $group: {
                _id: '$buildId',
                buildDefinitionId: { $first: '$buildDefinitionId' },
                zeroCountOperations: { $sum: '$zeroCountOperations' },
                usedOperations: { $sum: '$usedOperations' },
                createdAt: { $first: '$createdAt' },
            },
        },
        { $addFields: { buildId: '$_id' } },
        { $project: { _id: 0 } },
    ]).then(results => (results.length ? results[0] : null));
};
export const getWeeklyStubUsageSummary = async (queryContext) => {
    const Model = getModel(queryContext);
    const { startDate, endDate } = getQueryDates(queryContext);
    const { numberOfIntervals } = createIntervals(startDate, endDate);
    const [stubUsageForDefs, buildDefs] = await Promise.all([
        getStubUsage(queryContext),
        getBuildDefIds(Model, queryContext),
    ]);
    const buildDefsWithStubUsage = buildDefs.map(buildDef => {
        const matchingStubUsage = stubUsageForDefs.filter(coverage => coverage.buildDefinitionId === buildDef.id.toString());
        return {
            buildDefId: buildDef.id.toString(),
            stubUsageByWeek: matchingStubUsage.sort(asc(byNum(w => w.weekIndex))),
        };
    });
    const continuousStubUsage = await Promise.all(buildDefsWithStubUsage.map(async ({ buildDefId, stubUsageByWeek }) => {
        return {
            buildDefId,
            stubUsageByWeek: await makeContinuous(queryContext, stubUsageByWeek, () => getOneOlderStubUsageForBuildDefinition(queryContext, buildDefId), item => ({
                buildDefinitionId: buildDefId,
                zeroCountOperations: (item === null || item === void 0 ? void 0 : item.zeroCountOperations) || 0,
                usedOperations: (item === null || item === void 0 ? void 0 : item.usedOperations) || 0,
            })),
        };
    }));
    return range(0, numberOfIntervals).map(weekIndex => {
        const weekStubUsage = continuousStubUsage.reduce((acc, { stubUsageByWeek }) => {
            const matchingStubUsage = stubUsageByWeek.find(coverage => coverage.weekIndex === weekIndex);
            if (matchingStubUsage) {
                acc.zeroCountOperations += matchingStubUsage.zeroCountOperations;
                acc.usedOperations += matchingStubUsage.usedOperations;
            }
            return acc;
        }, {
            zeroCountOperations: 0,
            usedOperations: 0,
        });
        return {
            weekIndex,
            zeroCountOperations: weekStubUsage.zeroCountOperations,
            usedOperations: weekStubUsage.usedOperations,
        };
    });
};
const filterByServiceType = (inputKey, serviceType) => {
    return {
        $addFields: {
            [inputKey]: {
                $filter: {
                    input: `$${inputKey}`,
                    as: 'item',
                    cond: { $eq: ['$$item.serviceType', serviceType] },
                },
            },
        },
    };
};
const reportsWithSpecmaticCoverageOrStubs = {
    $or: [
        { specmaticCoverage: { $exists: true } },
        { specmaticStubUsage: { $exists: true } },
    ],
};
const replaceRootWithLatestBuildAfterGroupingBuildsBy = (groupingKey) => [
    { $sort: { createdAt: 1 } },
    { $group: { _id: `$${groupingKey}`, build: { $last: '$$ROOT' } } },
    { $replaceRoot: { newRoot: '$build' } },
];
const unwindCoverageAndStubOperations = [
    {
        $unwind: {
            path: '$specmaticCoverage',
            preserveNullAndEmptyArrays: true,
        },
    },
    {
        $unwind: {
            path: '$specmaticCoverage.operations',
            preserveNullAndEmptyArrays: true,
        },
    },
    {
        $unwind: {
            path: '$specmaticStubUsage',
            preserveNullAndEmptyArrays: true,
        },
    },
    {
        $unwind: {
            path: '$specmaticStubUsage.operations',
            preserveNullAndEmptyArrays: true,
        },
    },
];
const groupHttpOperationsByBuildId = {
    $group: {
        _id: '$buildId',
        repoId: { $first: '$repoId' },
        repoName: { $first: '$repo' },
        serviceId: { $first: '$serviceId' },
        leafDirectory: { $first: '$specmaticConfigPath' },
        endpoints: {
            $addToSet: {
                specId: '$specmaticCoverage.specId',
                path: '$specmaticCoverage.operations.path',
                method: '$specmaticCoverage.operations.method',
            },
        },
        dependsOn: {
            $addToSet: {
                specId: '$specmaticStubUsage.specId',
                path: '$specmaticStubUsage.operations.path',
                method: '$specmaticStubUsage.operations.method',
            },
        },
    },
};
const groupKafkaChannelsByBuildId = {
    $group: {
        _id: '$buildId',
        repoId: { $first: '$repoId' },
        repoName: { $first: '$repo' },
        serviceId: { $first: '$serviceId' },
        leafDirectory: { $first: '$specmaticConfigPath' },
        channels: {
            $addToSet: {
                name: '$specmaticStubUsage.channels.name',
                type: '$specmaticStubUsage.channels.type',
            },
        },
    },
};
const unwindStubChannels = [
    {
        $unwind: {
            path: '$specmaticStubUsage',
            preserveNullAndEmptyArrays: true,
        },
    },
    {
        $unwind: {
            path: '$specmaticStubUsage.channels',
            preserveNullAndEmptyArrays: true,
        },
    },
];
const serviceServesEndpoint = (dependency) => (service) => service.endpoints.some(endpoint => endpoint.path === dependency.path &&
    endpoint.method === dependency.method &&
    endpoint.specId === dependency.specId);
const filterEmptyObjectsFrom = (inputKey) => {
    return {
        $addFields: {
            [inputKey]: {
                $filter: {
                    input: `$${inputKey}`,
                    as: 'item',
                    cond: { $ne: ['$$item', {}] },
                },
            },
        },
    };
};
const getHttpServices = (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            createdAt: { $lt: endDate },
            ...reportsWithSpecmaticCoverageOrStubs,
        }),
        filterByServiceType('specmaticCoverage', 'HTTP'),
        filterByServiceType('specmaticStubUsage', 'HTTP'),
        ...replaceRootWithLatestBuildAfterGroupingBuildsBy('buildDefinitionId'),
        { $addFields: { serviceId: { $concat: ['$repoId', '$specmaticConfigPath'] } } },
        ...unwindCoverageAndStubOperations,
        groupHttpOperationsByBuildId,
        filterEmptyObjectsFrom('endpoints'),
        filterEmptyObjectsFrom('dependsOn'),
        { $project: { _id: 0 } },
    ]);
};
const getKafkaServices = (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            createdAt: { $lt: endDate },
            ...reportsWithSpecmaticCoverageOrStubs,
        }),
        filterByServiceType('specmaticStubUsage', 'Kafka'),
        ...replaceRootWithLatestBuildAfterGroupingBuildsBy('buildDefinitionId'),
        { $addFields: { serviceId: { $concat: ['$repoId', '$specmaticConfigPath'] } } },
        ...unwindStubChannels,
        groupKafkaChannelsByBuildId,
        filterEmptyObjectsFrom('channels'),
        { $project: { _id: 0 } },
    ]);
};
const createHttpService = (service, md5ServiceId, services) => ({
    endpoints: service.endpoints.map(endpoint => ({
        ...endpoint,
        serviceId: md5ServiceId,
    })),
    dependsOn: service.dependsOn.map(dependency => {
        var _a;
        return ({
            ...dependency,
            serviceId: md5(((_a = services.find(serviceServesEndpoint(dependency))) === null || _a === void 0 ? void 0 : _a.serviceId) || ''),
        });
    }),
});
const createKafkaService = (service, md5ServiceId) => ({
    channels: service.channels.map(channel => ({
        ...channel,
        serviceId: md5ServiceId,
    })),
});
const combineMultipleServices = (servicesByType, serviceName) => {
    const combinedServices = new Map();
    servicesByType.forEach(({ services, type }) => {
        services.forEach(service => {
            const md5ServiceId = md5(service.serviceId);
            const matchingService = combinedServices.get(md5ServiceId) || {
                name: serviceName(service),
                serviceId: md5ServiceId,
                serviceTypes: {},
            };
            if (type === 'http') {
                matchingService.serviceTypes.http = createHttpService(service, md5ServiceId, services);
            }
            if (type === 'kafka') {
                matchingService.serviceTypes.kafka = createKafkaService(service, md5ServiceId);
            }
            combinedServices.set(md5ServiceId, matchingService);
        });
    });
    return [...combinedServices.values()];
};
const isMonorepoUsingServices = (services) => {
    const hasRepoBeenSeenTwice = new Map();
    services.forEach(service => {
        if (hasRepoBeenSeenTwice.has(service.repoId)) {
            hasRepoBeenSeenTwice.set(service.repoId, true);
        }
        else
            hasRepoBeenSeenTwice.set(service.repoId, false);
    });
    return (repoId) => hasRepoBeenSeenTwice.get(repoId) || false;
};
const serviceNameForServices = (services) => {
    const isMonorepo = isMonorepoUsingServices(services);
    return (service) => {
        const leafDirectory = path.dirname(service.leafDirectory).split(path.sep).at(-1);
        return isMonorepo(service.repoId) ? leafDirectory : service.repoName;
    };
};
export const getServiceGraph = async (queryContext) => {
    const services = await Promise.all([
        getHttpServices(queryContext).then(x => ({
            type: 'http',
            services: x,
        })),
        getKafkaServices(queryContext).then(x => ({ type: 'kafka', services: x })),
    ]);
    const serviceName = serviceNameForServices(services.flatMap(x => x.services));
    return combineMultipleServices(services, serviceName).sort(byString(x => x.serviceId));
};
const groupUniqueCoverageAndStubOperations = [
    {
        $group: {
            _id: '$buildId',
            buildId: { $first: '$buildId' },
            buildDefinitionId: { $first: '$buildDefinitionId' },
            createdAt: { $first: '$createdAt' },
            coverageOps: {
                $addToSet: {
                    specId: '$specmaticCoverage.specId',
                    path: '$specmaticCoverage.operations.path',
                    method: '$specmaticCoverage.operations.method',
                    responseCode: '$specmaticCoverage.operations.responseCode',
                },
            },
            stubOps: {
                $addToSet: {
                    specId: '$specmaticStubUsage.specId',
                    path: '$specmaticStubUsage.operations.path',
                    method: '$specmaticStubUsage.operations.method',
                    responseCode: '$specmaticStubUsage.operations.responseCode',
                },
            },
        },
    },
    filterEmptyObjectsFrom('coverageOps'),
    filterEmptyObjectsFrom('stubOps'),
];
const getConsumerProducerSpecAndOperationsCount = async (queryContext) => {
    const Model = getModel(queryContext);
    const { startDate, endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            createdAt: inDateRange(startDate, endDate),
            ...reportsWithSpecmaticCoverageOrStubs,
        }),
        filterByServiceType('specmaticCoverage', 'HTTP'),
        filterByServiceType('specmaticStubUsage', 'HTTP'),
        ...unwindCoverageAndStubOperations,
        ...groupUniqueCoverageAndStubOperations,
        { $sort: { createdAt: 1 } },
        {
            $group: {
                _id: {
                    weekIndex: weekIndexValue(startDate, '$createdAt'),
                    buildDefinitionId: '$buildDefinitionId',
                },
                coverageOps: { $last: '$coverageOps' },
                stubOps: { $last: '$stubOps' },
                latestBuildId: { $last: '$_id' },
            },
        },
        {
            $addFields: {
                weekIndex: '$_id.weekIndex',
                buildDefinitionId: '$_id.buildDefinitionId',
            },
        },
        { $project: { _id: 0 } },
    ]);
};
const getOlderConsumerProducerSpecAndOpsForBuildDef = async (queryContext, buildDefinitionId) => {
    const Model = getModel(queryContext);
    const { startDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            buildDefinitionId,
            createdAt: { $lt: startDate },
            ...reportsWithSpecmaticCoverageOrStubs,
        }),
        { $sort: { createdAt: -1 } },
        { $limit: 1 },
        filterByServiceType('specmaticCoverage', 'HTTP'),
        filterByServiceType('specmaticStubUsage', 'HTTP'),
        ...unwindCoverageAndStubOperations,
        ...groupUniqueCoverageAndStubOperations,
        { $project: { _id: 0 } },
    ]).then(results => (results.length ? results[0] : null));
};
export const getWeeklyConsumerProducerSpecAndOperationsCount = async (queryContext) => {
    const Model = getModel(queryContext);
    const { startDate, endDate } = getQueryDates(queryContext);
    const { numberOfIntervals } = createIntervals(startDate, endDate);
    const [consumerProducerSpecAndOpsCountForDefs, buildDefs] = await Promise.all([
        getConsumerProducerSpecAndOperationsCount(queryContext),
        getBuildDefIds(Model, queryContext),
    ]);
    const buildDefsWithCount = buildDefs.map(buildDef => {
        const matchingDefsCount = consumerProducerSpecAndOpsCountForDefs.filter(coverage => coverage.buildDefinitionId === buildDef.id.toString());
        return {
            buildDefId: buildDef.id.toString(),
            countByWeek: matchingDefsCount.sort(asc(byNum(w => w.weekIndex))),
        };
    });
    const continuousCount = await Promise.all(buildDefsWithCount.map(async ({ buildDefId, countByWeek }) => {
        return {
            buildDefId,
            countByWeek: await makeContinuous(queryContext, countByWeek, () => getOlderConsumerProducerSpecAndOpsForBuildDef(queryContext, buildDefId), item => ({
                buildDefinitionId: buildDefId,
                coverageOps: (item === null || item === void 0 ? void 0 : item.coverageOps) || [],
                stubOps: (item === null || item === void 0 ? void 0 : item.stubOps) || [],
            })),
        };
    }));
    return range(0, numberOfIntervals).map(weekIndex => {
        const weeklyCount = continuousCount.reduce((acc, { countByWeek }) => {
            const matchingWeek = countByWeek.find(coverage => coverage.weekIndex === weekIndex);
            if (matchingWeek) {
                acc.coverageOps = acc.coverageOps.concat(matchingWeek.coverageOps);
                acc.stubOps = acc.stubOps.concat(matchingWeek.stubOps);
            }
            return acc;
        }, { coverageOps: [], stubOps: [] });
        return {
            weekIndex,
            count: intersection(weeklyCount.coverageOps, weeklyCount.stubOps).length,
            operationUsedAsStubOnly: difference(weeklyCount.stubOps, weeklyCount.coverageOps)
                .length,
            operationsUsedAsTestsOnly: difference(weeklyCount.coverageOps, weeklyCount.stubOps)
                .length,
            total: union(weeklyCount.coverageOps, weeklyCount.stubOps).length,
        };
    });
};
const matchCentralRepoReports = (queryContext, 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
additionalClauses = {}) => ({
    $match: {
        ...precondition(queryContext),
        specmaticCentralRepoReport: { $exists: true },
        ...additionalClauses,
    },
});
export const getSpecmaticCentralRepoReportOperations = async (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        matchCentralRepoReports(queryContext, { createdAt: { $lt: endDate } }),
        ...replaceRootWithLatestBuildAfterGroupingBuildsBy('repoId'),
        filterByServiceType('specmaticCentralRepoReport', 'HTTP'),
        {
            $addFields: {
                totalOps: {
                    $sum: {
                        $map: {
                            input: '$specmaticCentralRepoReport',
                            as: 'report',
                            in: { $size: '$$report.operations' },
                        },
                    },
                },
            },
        },
        { $group: { _id: null, totalOps: { $sum: '$totalOps' } } },
        { $project: { _id: 0, totalOps: 1 } },
    ]).then(results => (results.length ? results[0].totalOps : null));
};
export const getStubOperationsCount = async (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            specmaticStubUsage: { $exists: true },
            createdAt: { $lt: endDate },
        }),
        ...replaceRootWithLatestBuildAfterGroupingBuildsBy('buildDefinitionId'),
        { $unwind: '$specmaticStubUsage' },
        { $unwind: '$specmaticStubUsage.operations' },
        {
            $group: {
                _id: null,
                centralRepOpsCount: {
                    $sum: {
                        $cond: {
                            if: { $ifNull: ['$specmaticStubUsage.repository', false] },
                            then: 1,
                            else: 0,
                        },
                    },
                },
                projectRepOpsCount: {
                    $sum: {
                        $cond: {
                            if: { $ifNull: ['$specmaticStubUsage.repository', false] },
                            then: 0,
                            else: 1,
                        },
                    },
                },
            },
        },
        { $project: { _id: 0 } },
    ]).then(results => results.length ? results[0] : { centralRepOpsCount: 0, projectRepOpsCount: 0 });
};
export const getContractStatsAsChunks = async (queryContext, onChunk) => {
    const sendChunk = (key) => (data) => {
        onChunk({ [key]: data });
    };
    await Promise.all([
        getWeeklyApiCoverageSummary(queryContext).then(sendChunk('weeklyApiCoverage')),
        getWeeklyStubUsageSummary(queryContext).then(sendChunk('weeklyStubUsage')),
        getWeeklyConsumerProducerSpecAndOperationsCount(queryContext).then(sendChunk('weeklyConsumerProducerSpecAndOps')),
        getSpecmaticCentralRepoReportOperations(queryContext).then(sendChunk('specmaticCentralRepoReportOperations')),
        getStubOperationsCount(queryContext).then(sendChunk('stubOperationsCount')),
    ]);
};
const getLatestApiCoverageBySpecIds = async (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            createdAt: { $lt: endDate },
            specmaticCoverage: { $exists: true },
        }),
        filterByServiceType('specmaticCoverage', 'HTTP'),
        ...replaceRootWithLatestBuildAfterGroupingBuildsBy('buildDefinitionId'),
        { $unwind: '$specmaticCoverage' },
        {
            $addFields: {
                coveredOperations: {
                    $size: {
                        $filter: {
                            input: '$specmaticCoverage.operations',
                            as: 'operation',
                            cond: { $eq: ['$$operation.coverageStatus', 'covered'] },
                        },
                    },
                },
            },
        },
        {
            $group: {
                _id: '$specmaticCoverage.specId',
                coveredOperations: { $sum: '$coveredOperations' },
            },
        },
        { $project: { _id: 0, specId: '$_id', coveredOperations: 1 } },
    ]);
};
const getLatestStubUsageBySpecIds = async (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        buildReportsWithSpecmatic(queryContext, {
            createdAt: { $lt: endDate },
            specmaticStubUsage: { $exists: true },
        }),
        filterByServiceType('specmaticStubUsage', 'HTTP'),
        ...replaceRootWithLatestBuildAfterGroupingBuildsBy('buildDefinitionId'),
        { $unwind: '$specmaticStubUsage' },
        {
            $addFields: {
                zeroCountOperations: {
                    $size: {
                        $filter: {
                            input: '$specmaticStubUsage.operations',
                            as: 'operation',
                            cond: { $eq: ['$$operation.count', 0] },
                        },
                    },
                },
                usedOperations: {
                    $size: {
                        $filter: {
                            input: '$specmaticStubUsage.operations',
                            as: 'operation',
                            cond: { $ne: ['$$operation.count', 0] },
                        },
                    },
                },
            },
        },
        {
            $group: {
                _id: '$specmaticStubUsage.specId',
                zeroCountOperations: { $sum: '$zeroCountOperations' },
                usedOperations: { $sum: '$usedOperations' },
            },
        },
        {
            $project: {
                _id: 0,
                specId: '$_id',
                zeroCountOperations: 1,
                usedOperations: 1,
            },
        },
    ]);
};
const getTotalOperationsBySpecIds = async (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    return Model.aggregate([
        matchCentralRepoReports(queryContext, { createdAt: { $lt: endDate } }),
        ...replaceRootWithLatestBuildAfterGroupingBuildsBy('repoId'),
        filterByServiceType('specmaticCentralRepoReport', 'HTTP'),
        { $unwind: '$specmaticCentralRepoReport' },
        {
            $group: {
                _id: '$specmaticCentralRepoReport.specId',
                totalOps: { $sum: { $size: '$specmaticCentralRepoReport.operations' } },
            },
        },
        { $project: { _id: 0, specId: '$_id', totalOps: 1 } },
    ]);
};
export const getSpecmaticContractsListing = async (queryContext) => {
    const Model = getModel(queryContext);
    const { endDate } = getQueryDates(queryContext);
    const [specs, coverageBySpecIds, stubUsageBySpecIds, totalOpsBySpecIds] = await Promise.all([
        Model.aggregate([
            matchCentralRepoReports(queryContext, { createdAt: { $lt: endDate } }),
            ...replaceRootWithLatestBuildAfterGroupingBuildsBy('repoId'),
            { $unwind: '$specmaticCentralRepoReport' },
            {
                $project: {
                    _id: 0,
                    buildDefinitionId: 1,
                    repoId: 1,
                    repo: 1,
                    repoUrl: 1,
                    specmaticCentralRepoReport: 1,
                },
            },
        ]),
        getLatestApiCoverageBySpecIds(queryContext),
        getLatestStubUsageBySpecIds(queryContext),
        getTotalOperationsBySpecIds(queryContext),
    ]);
    const filterCoverageForSpecIds = (coverageBySpecIds, specIds) => coverageBySpecIds.filter(coverage => specIds.includes(coverage.specId));
    const filterStubUsageForSpecIds = (stubUsageBySpecIds, specIds) => stubUsageBySpecIds.filter(stubUsage => specIds.includes(stubUsage.specId));
    const filterTotalOpsForSpecIds = (totalOpsBySpecIds, specIds) => totalOpsBySpecIds.filter(totalOps => specIds.includes(totalOps.specId));
    // specmaticCentralRepoReportDocs[0].specmaticCentralRepoReport.specification =
    //   'a/b/c/spec.yaml';
    const mergeIntoTree = (dir, specItem) => {
        const pathParts = specItem.specification.split('/');
        if (pathParts.length === 1) {
            // We should add stats for this file
            dir.specIds.push(specItem.specId);
            dir.coverage = filterCoverageForSpecIds(coverageBySpecIds, dir.specIds);
            dir.stubUsage = filterStubUsageForSpecIds(stubUsageBySpecIds, dir.specIds);
            dir.totalOps = filterTotalOpsForSpecIds(totalOpsBySpecIds, dir.specIds);
            return dir;
        }
        const matchingChildDirectoryIndex = dir.childDirectories.findIndex(directory => directory.directoryName === pathParts[0]);
        if (matchingChildDirectoryIndex !== -1) {
            // Found a matching child directory
            dir.childDirectories = dir.childDirectories.map((directory, index) => {
                if (index !== matchingChildDirectoryIndex)
                    return directory;
                return mergeIntoTree(directory, {
                    ...specItem,
                    specification: pathParts.slice(1).join('/'),
                });
            });
            return dir;
        }
        dir.childDirectories.push(mergeIntoTree({
            directoryName: pathParts[0],
            childDirectories: [],
            specIds: [],
            coverage: [],
            stubUsage: [],
            totalOps: [],
        }, {
            ...specItem,
            specification: pathParts.slice(1).join('/'),
        }));
        return dir;
    };
    const specsByRepoUrl = groupBy(prop('repoUrl'), specs);
    return Object.values(specsByRepoUrl).map(specs => {
        return {
            repoUrl: specs[0].repoUrl,
            dir: specs.reduce((acc, spec) => mergeIntoTree(acc, spec.specmaticCentralRepoReport), {
                directoryName: 'root',
                childDirectories: [],
                specIds: [],
                coverage: [],
                stubUsage: [],
                totalOps: [],
            }),
        };
    });
};
