import * as fs from 'node:fs';
import * as fsp from 'node:fs/promises';
import * as readline from 'node:readline/promises';
import clc from 'cli-color';
import path from 'node:path';
import os from 'node:os';
import { Checkupdates } from 'artix-checkupdates';
import { Gitea } from './gitea.js';
import { Gitlab } from './gitlab.js';
import { DefaultConf } from './artoolsconf.js';
import { snooze } from './snooze.js';
import { runCommand, isPasswordRequired } from './runCommand.js';
const PACKAGE_ORG = 'packages';
const SIGNFILE = path.join(os.tmpdir(), 'signfile');
/**
 * Formats text to be sent as a parameter to some command
 * @param param
 */
function escapeCommandParam(param) {
    return param.replace(/\\/g, "\\\\");
}
export class Pusher {
    _gitea;
    _gitlab;
    _config;
    _artools;
    _createdSignfile;
    constructor(config = {}, artoolsConf = DefaultConf) {
        this._gitea = null;
        this._artools = artoolsConf;
        this._config = config;
        this._createdSignfile = false;
        this._gitea = new Gitea({
            token: this._artools.giteaToken || ''
        });
        this._gitlab = new Gitlab();
    }
    async refreshGpg() {
        const sshSignMode = 'SSHKEYSIGN' in process.env;
        if (sshSignMode || await isPasswordRequired()) {
            console.log(clc.cyan('Refreshing signature...'));
            this._createdSignfile ||= await runCommand('touch', [SIGNFILE]);
            if (sshSignMode) {
                await runCommand('ssh-keygen', ['-Y', 'sign', '-f', path.resolve(process.env['SSHKEYSIGN']), '-n', ' git', SIGNFILE]);
            }
            else {
                await runCommand('gpg', ['-a', '--passphrase', escapeCommandParam(this._config.gpgpass || ''), '--batch', '--pinentry-mode', 'loopback', '--detach-sign', SIGNFILE]);
            }
            await fsp.rm(`${SIGNFILE}.${sshSignMode ? 'sig' : 'asc'}`);
        }
    }
    increment(pkg) {
        return new Promise(async (res, _) => {
            const pkgbuild = path.join(this._artools.workspace, 'artixlinux', pkg, 'PKGBUILD');
            let lines = [];
            const rl = readline.createInterface({
                input: fs.createReadStream(pkgbuild),
                output: process.stdout,
                terminal: false
            });
            rl.on('line', async (line) => {
                if (line.trim().startsWith('pkgrel')) {
                    const pkgrelLine = line.split('=');
                    if (pkgrelLine.length <= 1) {
                        throw new Error(`Failed to parse pkgrel line: \n${line}`);
                    }
                    const pkgrel = pkgrelLine[1].trim();
                    // let's not deal with floats in javascript
                    let num = pkgrel.split('.');
                    if (num.length > 1) {
                        num[1] = `${parseInt(num[1]) + 1}`;
                    }
                    else {
                        num.push('1');
                    }
                    lines.push(`pkgrel=${num.join('.')}`);
                }
                else {
                    lines.push(line);
                }
            });
            rl.on('close', async () => {
                await fsp.writeFile(pkgbuild, lines.join('\n') + '\n');
                res();
            });
        });
    }
    async isNewPackage(pkg) {
        const pkgbuild = path.join(this._artools.workspace, 'artixlinux', pkg, 'PKGBUILD');
        try {
            const stat = await fsp.stat(pkgbuild);
            return !stat.size;
        }
        catch {
            console.log('PKGBUILD doesn\'t exist. Assuming package is new.');
            console.info(`checked ${pkgbuild}`);
            return true;
        }
    }
    async createRepoImportArgs(job, packageName) {
        const args = ['repo', 'import'];
        if (job.nextTag !== false) {
            const tags = (await this._gitlab.getPackageTags(packageName));
            if (!tags || tags.length <= 0 || !tags[0]) {
                throw new Error(`could not get upstream tags for ${packageName}`);
            }
            const current = (await this._gitea?.getTags(PACKAGE_ORG, packageName))?.[0];
            if (!current) {
                throw new Error(`could not get current tag for ${packageName}`);
            }
            const matchTag = getUpstreamTag(current.name);
            let previousTag = null;
            let chosenTag = null;
            for (const tag of tags) {
                if (tag.name === matchTag) {
                    chosenTag = previousTag;
                    break;
                }
                previousTag = tag;
            }
            if (!chosenTag) {
                throw new Error(`could not determine next tag for ${packageName}`);
            }
            args.push('--tag', chosenTag.name);
        }
        args.push(packageName);
        return args;
    }
    async runJob(job) {
        const checkupdates = new Checkupdates();
        const gitea = this._gitea;
        this._artools.workspace = job.workspace || this._artools.workspace;
        gitea.setToken(job.giteaToken);
        if (!job.repo) {
            throw new Error('Must provide `repo` destination in config!');
        }
        if (job.increment && !this._artools?.workspace) {
            throw new Error('Must provide `directory` path in config if increment is enabled!');
        }
        if (job.increment && job.source) {
            throw new Error('increment can\'t be set to true for a move operation. Set increment to false or remove the source repo.');
        }
        console.log(clc.yellowBright('Running artix-checkupdates'));
        const actionable = job.increment ? job.packages : (await (!!job.source ? checkupdates.fetchLooseMovable(false, 8) : checkupdates.fetchUpgradable(false, 8))).map(res => res.basename);
        // order is IMPORTANT. Must be BLOCKING.
        for (let i = 0; i < (job.packages || []).length; i++) {
            const p = job.packages[i];
            let lastHash = '';
            if (!job.increment && !actionable.includes(p) && !await this.isNewPackage(p)) {
                console.log(clc.magenta(`${p} isn't marked as upgradable. Skipping.`));
                continue;
            }
            console.log((new Date()).toLocaleTimeString() + clc.magentaBright(` Package ${i}/${job.packages.length}`));
            while (!job.source) {
                try {
                    lastHash = (await gitea.getStatus(PACKAGE_ORG, p)).sha;
                    console.log(`current sha: ${lastHash}`);
                    break;
                }
                catch {
                    console.log(clc.red(`Failed to get status of ${p}. Retrying...`));
                    await snooze(30000);
                }
            }
            console.log(clc.yellowBright(`Pushing ${p} ...`));
            if (job.source) {
                try {
                    await this.refreshGpg();
                    await runCommand('artixpkg', ['repo', 'move', '-p', job.source, job.repo, p]);
                }
                catch {
                    console.log(clc.cyan(`Moving ${p} failed.`));
                }
            }
            else {
                if (job.increment) {
                    await this.increment(p);
                }
                else {
                    await runCommand('artixpkg', await this.createRepoImportArgs(job, p));
                }
                await this.refreshGpg();
                await runCommand('artixpkg', createRepoAddArgs(job, p));
            }
            console.log(clc.blueBright(`${p} commit pushed`));
            if (!job.source) {
                try {
                    await gitea.waitForBuild(lastHash, PACKAGE_ORG, p);
                    console.log(clc.greenBright(`${p} built successfully.`));
                }
                catch (ex) {
                    console.error(clc.redBright(`Failed on ${p} : ${gitea.getHomepage()}${PACKAGE_ORG}/${p}`));
                    throw ex;
                }
            }
        }
        console.log(clc.greenBright('SUCCESS: All packages built'));
        if (this._createdSignfile) {
            try {
                await fsp.rm(SIGNFILE);
            }
            catch {
                console.error(clc.red('failed to remove temp signfile'));
            }
        }
    }
}
function getUpstreamTag(tag) {
    if (!tag) {
        return tag;
    }
    const parts = tag.split('-');
    const i = parts.length - 1;
    const part = parts[i] || 'typescript sucks';
    parts[i] = part.substring(0, part.indexOf('.')) || part;
    return parts.join('-');
}
function createRepoAddArgs(job, packageName) {
    const args = ['repo', 'add', '-p'];
    if (job.nocheck) {
        args.push('--nocheck');
    }
    if (job.rebuild) {
        args.push('--rebuild');
    }
    args.push(job.repo, packageName);
    return args;
}
export default Pusher;
//# sourceMappingURL=pusher.js.map