import qs from 'qs';
import { head, map } from 'rambda';
import { byDate, desc } from 'sort-lib';
import fetch from './fetch-with-extras.js';
import { requiredMetrics } from '../stats-aggregators/code-quality.js';
import fetchWithDiskCache from './fetch-with-disk-cache.js';
import createPaginatedGetter from './create-paginated-getter.js';
import { pastDate, unique } from '../../utils.js';
import { latestBuildReportsForRepo } from '../../models/build-reports/index.js';
const projectsAtSonarServer = (config) => (sonarServer) => {
    var _a;
    const paginatedGet = createPaginatedGetter(config.cacheTimeMs, (_a = sonarServer.verifySsl) !== null && _a !== void 0 ? _a : true);
    return paginatedGet({
        url: `${sonarServer.url}/api/projects/search`,
        cacheFile: pageIndex => [
            'sonar',
            'projects',
            `${sonarServer.url.split('://')[1].replaceAll('.', '-')}-${pageIndex}`,
        ],
        headers: () => ({
            Authorization: `Basic ${Buffer.from(`${sonarServer.token}:`).toString('base64')}`,
        }),
        hasAnotherPage: previousResponse => previousResponse.data.paging.pageSize === previousResponse.data.components.length,
        qsParams: pageIndex => ({ ps: '500', p: (pageIndex + 1).toString() }),
    })
        .then(responses => responses.map(response => response.data.components.map(c => ({
        ...c,
        url: sonarServer.url,
        token: sonarServer.token,
    }))))
        .then(projects => projects.flat())
        .then(map(p => ({
        ...p,
        lastAnalysisDate: p.lastAnalysisDate ? new Date(p.lastAnalysisDate) : null,
    })));
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const getMeasureDefinitions = (config) => ({ url, token, verifySsl }) => {
    const { usingDiskCache } = fetchWithDiskCache(config.cacheTimeMs);
    return usingDiskCache(['sonar', 'measures-definitions', url.split('://')[1].replaceAll('.', '-')], () => fetch(`${url}/api/metrics/search?ps=200`, {
        headers: {
            Authorization: `Basic ${Buffer.from(`${token}:`).toString('base64')}`,
        },
        verifySsl: verifySsl !== null && verifySsl !== void 0 ? verifySsl : true,
    })).then(res => res.data.metrics);
};
const getMeasures = (config) => (sonarProject) => {
    const { usingDiskCache } = fetchWithDiskCache(config.cacheTimeMs);
    return usingDiskCache(['sonar', 'measures', sonarProject.key], () => {
        var _a;
        return fetch(`${sonarProject.url}/api/measures/component?${qs.stringify({
            component: sonarProject.key,
            metricKeys: requiredMetrics.join(','),
        })}`, {
            headers: {
                Authorization: `Basic ${Buffer.from(`${sonarProject.token}:`).toString('base64')}`,
            },
            verifySsl: (_a = sonarProject.verifySsl) !== null && _a !== void 0 ? _a : true,
        });
    }).then(res => {
        var _a;
        return ({
            name: sonarProject.name,
            url: `${sonarProject.url}/dashboard?id=${sonarProject.key}`,
            measures: ((_a = res.data.component) === null || _a === void 0 ? void 0 : _a.measures) || [],
            lastAnalysisDate: sonarProject.lastAnalysisDate
                ? new Date(sonarProject.lastAnalysisDate)
                : null,
        });
    });
};
const getQualityGateName = (config) => (sonarProject) => {
    const { usingDiskCache } = fetchWithDiskCache(config.cacheTimeMs);
    return usingDiskCache(['sonar', 'quality-gates', sonarProject.key], () => {
        var _a;
        return fetch(`${sonarProject.url}/api/qualitygates/get_by_project?${qs.stringify({
            project: sonarProject.key,
        })}`, {
            headers: {
                Authorization: `Basic ${Buffer.from(`${sonarProject.token}:`).toString('base64')}`,
            },
            verifySsl: (_a = sonarProject.verifySsl) !== null && _a !== void 0 ? _a : true,
        });
    }).then(res => res.data.qualityGate.name);
};
const getQualityGateHistory = (config) => (sonarProject) => {
    var _a;
    const paginatedGet = createPaginatedGetter(config.cacheTimeMs, (_a = sonarProject.verifySsl) !== null && _a !== void 0 ? _a : true);
    return paginatedGet({
        url: `${sonarProject.url}/api/measures/search_history`,
        cacheFile: pageIndex => [
            'sonar',
            'alert-status-history',
            `${sonarProject.url.split('://')[1].replaceAll('.', '-')}`,
            `${sonarProject.key}-${pageIndex}`,
        ],
        headers: () => ({
            Authorization: `Basic ${Buffer.from(`${sonarProject.token}:`).toString('base64')}`,
        }),
        hasAnotherPage: previousResponse => previousResponse.data.paging.total >
            previousResponse.data.paging.pageSize * previousResponse.data.paging.pageIndex,
        qsParams: pageIndex => ({
            ps: '500',
            p: (pageIndex + 1).toString(),
            component: sonarProject.key,
            metrics: 'alert_status',
            from: pastDate('182 days').toISOString().split('T')[0],
        }),
    })
        .then(responses => responses.map(response => response.data.measures.flatMap(m => m.history)))
        .then(history => history.flat())
        .then(history => history.map(item => ({
        value: item.value,
        date: new Date(item.date),
    })));
};
// #endregion
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const sortByLastAnalysedDate = desc(byDate(x => x.lastAnalysisDate));
const normliseNameForMatching = (name) => name.replaceAll('-', '_').toLowerCase();
// #region Attempt to find a sonar project
const attemptExactMatchFind = (repoName, sonarProjects) => {
    const matchingProjects = sonarProjects
        .filter(project => normliseNameForMatching(project.name) === normliseNameForMatching(repoName) &&
        Boolean(project.lastAnalysisDate))
        .sort(sortByLastAnalysedDate);
    return matchingProjects.length > 0 ? [matchingProjects[0]] : null;
};
const attemptStartsWithFind = (repoName, sonarProjects) => {
    const matchingProjects = sonarProjects
        .filter(project => normliseNameForMatching(project.name).startsWith(normliseNameForMatching(repoName)) && Boolean(project.lastAnalysisDate))
        .sort(sortByLastAnalysedDate);
    return matchingProjects.length > 0 ? matchingProjects : null;
};
const attemptMatchFromBuildReports = async (repoName, defaultBranch, sonarProjects, parseReports) => {
    if (!defaultBranch)
        return null;
    const buildReports = await parseReports(repoName);
    const projectKeys = unique(buildReports.map(({ sonarProjectKey }) => sonarProjectKey));
    const matchingSonarProjects = sonarProjects.filter(({ key }) => projectKeys.includes(key));
    return matchingSonarProjects.length ? matchingSonarProjects : null;
};
const attemptMatchByRepoName = (repoName, sonarProjects) => attemptExactMatchFind(repoName, sonarProjects) ||
    attemptStartsWithFind(repoName, sonarProjects);
const getMatchingSonarProjects = async (repoName, defaultBranch, sonarProjects, parseReports) => {
    const sonarProjectsFromBuildReports = await attemptMatchFromBuildReports(repoName, defaultBranch, sonarProjects, parseReports);
    return sonarProjectsFromBuildReports || attemptMatchByRepoName(repoName, sonarProjects);
};
// #endregion
const sonarProjectsCache = new Map();
const getSonarProjects = (config) => {
    if (!sonarProjectsCache.has(config)) {
        sonarProjectsCache.set(config, Promise.all((config.sonar || []).map(projectsAtSonarServer(config))).then(list => list.flat()));
    }
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    return sonarProjectsCache.get(config);
};
const dedupeSonarProjectsByKey = (sonarProjects) => sonarProjects.filter(sp => {
    const matchingProjects = sonarProjects.filter(s => s.key === sp.key);
    if (matchingProjects.length < 2)
        return true;
    return (head(matchingProjects
        .filter(p => p.lastAnalysisDate)
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        .sort(desc(byDate(p => p.lastAnalysisDate)))) === sp);
});
export default (config) => {
    const sonarProjects = getSonarProjects(config);
    // const measuresDefinition = Promise.all((config.sonar || []).map(getMeasureDefinitions(config)))
    //   .then(zip(config.sonar || []))
    //   .then(list => (
    //     (serverConfig: SonarConfig) => (
    //       list.find(server => server[0].url === serverConfig.url)?.[1] || []
    //     )
    //   ));
    return (collectionName, projectName) => async (repoName, defaultBranch) => {
        const matchingSonarProjects = await getMatchingSonarProjects(repoName, defaultBranch, await sonarProjects, latestBuildReportsForRepo(collectionName, projectName));
        if (!matchingSonarProjects)
            return null;
        return Promise.all(dedupeSonarProjectsByKey(matchingSonarProjects).map(sonarProject => Promise.all([
            getMeasures(config)(sonarProject),
            getQualityGateName(config)(sonarProject),
            getQualityGateHistory(config)(sonarProject),
            // measuresDefinition
        ]).then(([measures, qualityGateName, history]) => ({
            ...measures,
            qualityGateName,
            qualityGateHistory: history,
            // measuresDefinition: measuresDefinition(sonarProject)
        }))));
    };
};
