import { always, head, last, pipe, prop } from 'rambda';
import { asc, byNum } from 'sort-lib';
import { exists } from '../../../shared/utils.js';
import { isMaster, weeks } from '../../utils.js';
const createCondition = (condition) => ({
    type: condition.conditionType,
    name: condition.name,
});
const barePipeline = (release) => ({
    definitionId: release.releaseDefinitionId,
    name: release.releaseDefinitionName,
    url: release.releaseDefinitionUrl.replace('_apis/Release/definitions/', '_release?definitionId='),
    envs: {},
    attempts: [],
});
const getArtifactDetails = (release) => release.artifacts
    .map(artifact => {
    var _a, _b, _c;
    const repoName = (_a = artifact.definition) === null || _a === void 0 ? void 0 : _a.repositoryName;
    const repoId = (_b = artifact.definition) === null || _b === void 0 ? void 0 : _b.repositoryId;
    const branchName = (_c = artifact.definition) === null || _c === void 0 ? void 0 : _c.branch;
    if (!repoName || !repoId || !branchName)
        return;
    return { repoName, repoId, branchName };
})
    .filter(exists);
export const aggregateReleasesIntoPipelines = (releases) => {
    const combinedReleases = releases.reduce((acc, release) => {
        acc[release.releaseDefinitionId] =
            acc[release.releaseDefinitionId] || barePipeline(release);
        acc[release.releaseDefinitionId].attempts.push({
            id: release.id,
            name: release.name,
            reposAndBranches: getArtifactDetails(release),
            progression: release.environments
                .filter(env => { var _a; return env.status !== 'notStarted' && ((_a = env.deploySteps) === null || _a === void 0 ? void 0 : _a.length); })
                .map(env => ({
                name: env.name,
                rank: env.rank,
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                date: last(env.deploySteps).lastModifiedOn,
                state: env.status,
            }))
                .filter(exists)
                .sort(asc(byNum(prop('rank')))),
        });
        acc[release.releaseDefinitionId].envs = release.environments.reduce((acc, env) => {
            if (!acc[env.name]) {
                acc[env.name] = {
                    name: env.name,
                    conditions: env.conditions.map(createCondition),
                    rank: env.rank,
                };
            }
            else if (!acc[env.name].conditions.length) {
                acc[env.name].conditions = env.conditions.map(createCondition);
            }
            return acc;
        }, acc[release.releaseDefinitionId].envs);
        return acc;
    }, {});
    return Object.values(combinedReleases).map(release => {
        const { envs, ...rest } = release;
        return {
            ...rest,
            envs: Object.values(envs).sort(asc(byNum(prop('rank')))),
        };
    });
};
const didAttemptGoAheadUsing = (pipeline, ignoreStagesBefore) => {
    const firstMatchingStage = head(pipeline.envs.filter(env => env.name.toLowerCase().includes(ignoreStagesBefore.toLowerCase())));
    if (!firstMatchingStage)
        return always(false);
    return (attempt) => {
        const lastProgression = last(attempt.progression);
        if (!lastProgression)
            return false;
        const attemptProgressionMaxRank = lastProgression.rank;
        return attemptProgressionMaxRank >= firstMatchingStage.rank;
    };
};
const getReposAndBranches = (pipeline, ignoreStagesBefore) => {
    const didAttemptGoAhead = ignoreStagesBefore
        ? didAttemptGoAheadUsing(pipeline, ignoreStagesBefore)
        : always(true);
    const isAttemptIn = (week) => (attempt) => {
        const lastProgression = last(attempt.progression);
        if (!lastProgression)
            return false;
        const attemptProgressionDate = lastProgression.date;
        return week(attemptProgressionDate);
    };
    const isAttemptFromMaster = (attempt) => {
        if (attempt.reposAndBranches.length === 0)
            return false;
        return attempt.reposAndBranches.every(pipe(prop('branchName'), isMaster));
    };
    const masterOnlyReleasesByWeek = weeks
        .map(week => pipeline.attempts.filter(isAttemptIn(week)))
        .map(attempts => attempts.reduce((acc, attempt) => {
        acc.total += 1;
        acc.master += isAttemptFromMaster(attempt) ? 1 : 0;
        return acc;
    }, { total: 0, master: 0 }));
    const { repos, stageInfo, attempts } = pipeline.attempts.reduce((acc, attempt) => {
        const attemptWentAhead = didAttemptGoAhead(attempt);
        attempt.reposAndBranches.forEach(({ repoId, repoName, branchName }) => {
            acc.repos[repoId] = acc.repos[repoId] || {
                name: repoName,
                branches: new Set(),
                additionalBranches: new Set(),
            };
            if (attemptWentAhead) {
                acc.repos[repoId].branches.add(branchName);
                acc.repos[repoId].additionalBranches.delete(branchName);
            }
            else if (!acc.repos[repoId].branches.has(branchName)) {
                acc.repos[repoId].additionalBranches.add(branchName);
            }
        });
        attempt.progression.forEach(stage => {
            const stageInfo = acc.stageInfo.get(stage.name) || { successful: 0, total: 0 };
            stageInfo.total += 1;
            if (stage.state === 'succeeded')
                stageInfo.successful += 1;
            acc.stageInfo.set(stage.name, stageInfo);
        });
        if (attemptWentAhead) {
            acc.attempts.total += 1;
            if (isAttemptFromMaster(attempt))
                acc.attempts.master += 1;
        }
        return acc;
    }, {
        repos: {},
        stageInfo: new Map(),
        attempts: { total: 0, master: 0, byWeek: masterOnlyReleasesByWeek },
    });
    return {
        repos: Object.entries(repos).reduce((acc, [repoId, { name, branches, additionalBranches }]) => {
            acc[repoId] = {
                name,
                branches: [...branches],
                additionalBranches: additionalBranches.size > 0 ? [...additionalBranches] : undefined,
            };
            return acc;
        }, {}),
        attempts,
        stageCounts: pipeline.envs
            .filter(({ name }) => stageInfo.has(name))
            .sort(asc(byNum(prop('rank'))))
            .map(stage => {
            var _a, _b;
            return ({
                name: stage.name,
                successful: ((_a = stageInfo.get(stage.name)) === null || _a === void 0 ? void 0 : _a.successful) || 0,
                total: ((_b = stageInfo.get(stage.name)) === null || _b === void 0 ? void 0 : _b.total) || 0,
            });
        }),
    };
};
export default (projectConfig, releases, policyConfigurationByRepoId) => {
    const { ignoreStagesBefore } = projectConfig.releasePipelines;
    const pipelines = aggregateReleasesIntoPipelines(releases).map(pipeline => ({
        id: pipeline.definitionId,
        name: pipeline.name,
        url: pipeline.url,
        ...getReposAndBranches(pipeline, ignoreStagesBefore),
    }));
    const policies = pipelines.reduce((acc, pipeline) => {
        Object.entries(pipeline.repos).forEach(([repoId, { branches, additionalBranches }]) => {
            [...branches, ...(additionalBranches || [])].forEach(branch => {
                acc[repoId] = acc[repoId] || {};
                acc[repoId][branch] = policyConfigurationByRepoId(repoId, branch);
            });
        });
        return acc;
    }, {});
    return { pipelines, policies };
};
export const formatReleaseDefinition = (environments) => environments.sort(asc(byNum(prop('rank')))).map(env => ({
    name: env.name,
    rank: env.rank,
    conditions: env.conditions.map(createCondition),
}));
