import { join } from 'node:path';
import { readFile } from 'node:fs/promises';
import { always, anyPass, applySpec, compose, filter, length, map, not, pipe, prop, subtract, T, } from 'rambda';
import { isAfter, queryPeriodDays, weekLimits, weeks } from '../utils.js';
import { noGroup, isNewInTimeRange, isWIP, isWIPInTimeRange, timeDifference, totalCycleTime, totalWorkCenterTime, } from '../../shared/work-item-utils.js';
import { incrementBy, incrementIf, count } from '../../shared/reducer-utils.js';
import { isPipelineInGroup, masterDeploysCount, masterOnlyReleasesByWeek, normalizePolicy, pipelineHasStageNamed, pipelineHasStartingArtifact, pipelineMeetsBranchPolicyRequirements, pipelineUsesStageNamed, totalUsageByEnvironment, } from '../../shared/pipeline-utils.js';
import { buildPipelines, healthyBranches, isInactive, isYmlPipeline, newSonarSetupsByWeek, reposWithPipelines, sonarCountsByWeek, totalBuilds, totalBuildsByWeek, totalCoverage, totalCoverageByWeek, totalSuccessfulBuildsByWeek, totalTests, totalTestsByWeek, totalUsingCentralTemplate, } from '../../shared/repo-utils.js';
import { divide, exists, mapObj, toPercentage } from '../../shared/utils.js';
const looksLikeDate = (value) => /\d{4}-[01]\d-[0-3]\dT[0-2](?:\d:[0-5]){2}\d(.*Z)/.test(value);
const parseDate = (_, value) => {
    if (typeof value !== 'string')
        return value;
    if (!looksLikeDate(value))
        return value;
    return new Date(value);
};
const parseFile = async (collection, project, file) => JSON.parse(await readFile(join(process.cwd(), 'data', collection, project, file), 'utf8'), parseDate);
const getOverview = async (collection, project) => (await parseFile(collection, project, 'overview.json'))
    .overview;
const getRepoAnalysis = async (collection, project) => (await parseFile(collection, project, 'repos.json')).repos;
const getReleaseAnalysis = async (collection, project) => parseFile(collection, project, 'releases.json');
const groupType = (group) => {
    const remainingKey = Object.entries(group).find(([key]) => key !== 'collection' && key !== 'project');
    return remainingKey || 'project';
};
const matchingResults = (group, results) => {
    const gt = groupType(group);
    return results.reduce(async (pAcc, result) => {
        if (result.collectionConfig.name !== group.collection)
            return pAcc;
        if (result.projectConfig.name === group.project ||
            result.projectConfig.name === group.portfolioProject) {
            const acc = await pAcc;
            const overview = await getOverview(result.collectionConfig.name, result.projectConfig.name);
            acc.push({
                collection: result.collectionConfig.name,
                project: result.projectConfig.name,
                workItems: Object.values(overview.byId).filter(workItem => {
                    var _a;
                    return (_a = workItem.filterBy) === null || _a === void 0 ? void 0 : _a.some(filter => filter.label === gt[0] && filter.tags.includes(gt[1]));
                }),
                workItemTypes: overview.types,
                groups: overview.groups,
                workItemTimes: overview.times,
            });
            return acc;
        }
        return pAcc;
    }, Promise.resolve([]));
};
const addGroupToWorkItem = (groups) => (workItem) => ({
    ...workItem,
    group: workItem.groupId ? groups[workItem.groupId] : undefined,
});
const mergeResults = (results) => results.reduce((acc, result) => {
    acc.workItems = acc.workItems.concat(result.workItems.map(addGroupToWorkItem(result.groups)));
    acc.workItemTypes = {
        ...acc.workItemTypes,
        ...result.workItemTypes,
    };
    acc.workItemTimes = {
        ...acc.workItemTimes,
        ...result.workItemTimes,
    };
    return acc;
}, { workItems: [], workItemTypes: {}, workItemTimes: {} });
const groupKey = (group) => (group ? group.name : noGroup);
const hasWorkItemCompleted = (workItemTimes) => (isMatchingDate) => (workItem) => {
    const { start, end } = workItemTimes(workItem);
    return Boolean(start && end && isMatchingDate(new Date(end)));
};
const organiseWorkItemsIntoGroups = (workItems) => workItems.reduce((acc, workItem) => {
    acc[workItem.typeId] = acc[workItem.typeId] || {};
    acc[workItem.typeId][groupKey(workItem.group)] =
        acc[workItem.typeId][groupKey(workItem.group)] || [];
    acc[workItem.typeId][groupKey(workItem.group)].push(workItem);
    return acc;
}, {});
const processItemsInGroup = pipe(mapObj, mapObj);
const computeTimeDifference = (workItemTimes) => (start, end) => (workItem) => {
    const wits = workItemTimes(workItem);
    const { [start]: startTime } = wits;
    const endTime = end ? wits[end] : undefined;
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    return timeDifference({ start: startTime, end: endTime });
};
const analyseWorkItems = (results, isInQueryPeriod, projectConfig) => {
    const workItemTimes = (wi) => results.workItemTimes[wi.id];
    const workItemType = (witId) => results.workItemTypes[witId];
    const computeTimeDifferenceBetween = computeTimeDifference(workItemTimes);
    const wasWorkItemCompletedIn = hasWorkItemCompleted(workItemTimes);
    const wasWorkItemCompletedInQueryPeriod = wasWorkItemCompletedIn(isInQueryPeriod);
    const wipWorkItems = isWIP(workItemTimes, (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.workitems.ignoreForWIP) || []);
    const isOfType = (type) => (workItem) => results.workItemTypes[workItem.typeId].name[0] === type;
    const leakage = isNewInTimeRange(workItemType, workItemTimes);
    const flowEfficiency = (() => {
        const tct = totalCycleTime(workItemTimes);
        const wct = totalWorkCenterTime(workItemTimes);
        return (wis) => {
            const total = tct(wis);
            if (total === 0) {
                return { total: 0, wcTime: 0 };
            }
            const wcTime = wct(wis);
            return { total, wcTime };
        };
    })();
    const isWIPIn = ([, weekEnd]) => isWIPInTimeRange(workItemTimes, (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.workitems.ignoreForWIP) || [])((d, type) => {
        if (type === 'start')
            return d <= weekEnd;
        return d < weekEnd;
    });
    return pipe(filter(anyPass([isOfType('Feature'), isOfType('Bug'), isOfType('User Story')])), organiseWorkItemsIntoGroups, processItemsInGroup(applySpec({
        count: length,
        velocity: pipe(filter(wasWorkItemCompletedInQueryPeriod), length),
        velocityByWeek: (wis) => weeks.map(week => wis.filter(wasWorkItemCompletedIn(week)).length),
        cycleTime: pipe(filter(wasWorkItemCompletedInQueryPeriod), map(computeTimeDifferenceBetween('start', 'end'))),
        cycleTimeByWeek: (wis) => weeks.map(week => wis
            .filter(wasWorkItemCompletedIn(week))
            .map(computeTimeDifferenceBetween('start', 'end'))),
        changeLeadTime: pipe(filter(wasWorkItemCompletedInQueryPeriod), map(computeTimeDifferenceBetween('devComplete', 'end'))),
        changeLeadTimeByWeek: (wis) => weeks.map(week => wis
            .filter(wasWorkItemCompletedIn(week))
            .map(computeTimeDifferenceBetween('devComplete', 'end'))),
        flowEfficiency: pipe(filter(wasWorkItemCompletedInQueryPeriod), flowEfficiency),
        flowEfficiencyByWeek: (wis) => weeks.map(week => flowEfficiency(wis.filter(wasWorkItemCompletedIn(week)))),
        wipTrend: (wis) => weekLimits.map(limit => wis.filter(isWIPIn(limit)).length),
        wipCount: pipe(filter(wipWorkItems), length),
        wipAge: pipe(filter(wipWorkItems), map(computeTimeDifferenceBetween('start'))),
        leakage: pipe(filter(leakage(isInQueryPeriod)), length),
        leakageByWeek: (wis) => weeks.map(week => wis.filter(leakage(week)).length),
    })))(results.workItems);
};
const summariseResults = async (config, results) => {
    const { summaryPageGroups } = config.azure;
    if (!summaryPageGroups) {
        return {
            groups: [],
            workItemTypes: {},
            lastUpdateDate: new Date().toISOString(),
        };
    }
    const summarised = await Promise.all(summaryPageGroups.map(async (group) => {
        var _a, _b;
        // eslint-disable-next-line unicorn/consistent-destructuring
        const projectConfig = (_a = config.azure.collections
            .find(c => c.name === group.collection)) === null || _a === void 0 ? void 0 : _a.projects.find(p => p.name === group.project);
        const match = await matchingResults(group, results);
        if (!match.length)
            return null;
        const mergedResults = mergeResults(match);
        const resultsForThisProject = results.find(r => r.collectionConfig.name === group.collection &&
            r.projectConfig.name === group.project);
        if (!resultsForThisProject)
            return null;
        const [repoAnalysis, releaseAnalysis] = await Promise.all([
            getRepoAnalysis(resultsForThisProject.collectionConfig.name, resultsForThisProject.projectConfig.name),
            getReleaseAnalysis(resultsForThisProject.collectionConfig.name, resultsForThisProject.projectConfig.name),
        ]);
        const repoNames = ((_b = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.groupRepos) === null || _b === void 0 ? void 0 : _b.groups[groupType(group)[1]]) || [];
        const matchingRepos = (repoNames) => {
            const allRepos = repoAnalysis || [];
            return repoNames.length === 0
                ? allRepos
                : allRepos.filter(r => repoNames.includes(r.name));
        };
        const repoStats = () => {
            const matches = matchingRepos(repoNames);
            const matchesExcludingInactive = matches.filter(compose(not, isInactive));
            const codeQuality = (repos) => repos.reduce((acc, repo) => ({
                ...(repo.codeQuality || []).reduce((acc, q) => ({
                    ...acc,
                    pass: acc.pass + (q.quality.gate === 'pass' ? 1 : 0),
                    warn: acc.warn + (q.quality.gate === 'warn' ? 1 : 0),
                    fail: acc.fail + (q.quality.gate === 'fail' ? 1 : 0),
                    sonarProjects: acc.sonarProjects + 1,
                }), acc),
                configured: acc.configured + (repo.codeQuality ? 1 : 0),
            }), {
                pass: 0,
                warn: 0,
                fail: 0,
                configured: 0,
                sonarProjects: 0,
            });
            const coverage = totalCoverage(matchesExcludingInactive);
            const coverageByWeek = totalCoverageByWeek(matchesExcludingInactive);
            const pipelines = buildPipelines(matchesExcludingInactive);
            return {
                repos: length(matchesExcludingInactive),
                excluded: pipe(length, subtract(length(matches)))(matchesExcludingInactive),
                tests: totalTests(matchesExcludingInactive),
                testsByWeek: totalTestsByWeek(matchesExcludingInactive),
                builds: {
                    total: totalBuilds(matchesExcludingInactive),
                    successful: count(incrementBy(repo => {
                        var _a;
                        return count(incrementBy(prop('success')))(((_a = repo.builds) === null || _a === void 0 ? void 0 : _a.pipelines) || []);
                    }))(matchesExcludingInactive),
                    byWeek: totalBuildsByWeek(matchesExcludingInactive),
                    successfulByWeek: totalSuccessfulBuildsByWeek(matchesExcludingInactive),
                },
                codeQuality: codeQuality(matchesExcludingInactive),
                newSonarSetupsByWeek: newSonarSetupsByWeek(matchesExcludingInactive),
                sonarCountsByWeek: sonarCountsByWeek(matchesExcludingInactive),
                coverage: divide(coverage.covered, coverage.total).map(toPercentage).getOr('-'),
                coverageByWeek,
                ymlPipelines: {
                    count: pipelines.filter(isYmlPipeline).length,
                    total: pipelines.length,
                },
                hasPipelines: reposWithPipelines(matchesExcludingInactive).length,
                usesCentralTemplate: totalUsingCentralTemplate(matchesExcludingInactive),
                healthyBranches: healthyBranches(matchesExcludingInactive),
            };
        };
        const pipelineStats = () => {
            const matchingPipelines = (releaseAnalysis.pipelines || []).filter(repoNames.length === 0 ? T : isPipelineInGroup(groupType(group)[1], repoNames));
            return {
                pipelines: length(matchingPipelines),
                stages: ((projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.releasePipelines.stagesToHighlight) || []).map(stage => applySpec({
                    name: always(stage),
                    exists: count(incrementIf(pipelineHasStageNamed(stage))),
                    used: count(incrementIf(pipelineUsesStageNamed(stage))),
                })(matchingPipelines)),
                masterOnlyPipelines: masterDeploysCount(matchingPipelines),
                startsWithArtifact: count(incrementIf(pipelineHasStartingArtifact))(matchingPipelines),
                conformsToBranchPolicies: count(incrementIf(pipelineMeetsBranchPolicyRequirements((repoId, branch) => { var _a; return normalizePolicy(((_a = releaseAnalysis.policies[repoId]) === null || _a === void 0 ? void 0 : _a[branch]) || {}); })))(matchingPipelines),
                usageByEnvironment: totalUsageByEnvironment(projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.environments)(matchingPipelines),
                masterOnlyReleasesByWeek: masterOnlyReleasesByWeek(matchingPipelines),
            };
        };
        return {
            ...group,
            groupName: groupType(group)[1],
            workItems: analyseWorkItems(mergedResults, isAfter(`${queryPeriodDays(config)} days`), projectConfig),
            repoStats: repoStats(),
            pipelineStats: pipelineStats(),
            workItemTypes: mergedResults.workItemTypes,
            environments: projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.environments,
        };
    }));
    return summarised.filter(exists).reduce((acc, { workItemTypes, ...rest }) => {
        acc.groups.push(rest);
        acc.workItemTypes = {
            ...acc.workItemTypes,
            ...Object.entries(workItemTypes).reduce((acc, [key, value]) => {
                if (value.name[0] === 'User Story' ||
                    value.name[0] === 'Feature' ||
                    value.name[0] === 'Bug') {
                    acc[key] = value;
                }
                return acc;
            }, {}),
        };
        return acc;
    }, { groups: [], workItemTypes: {}, lastUpdateDate: new Date().toISOString() });
};
export default summariseResults;
