import fs from 'node:fs';
import constants from '../constants.js';
import CSSClient from '../cssClient.js';
import OutputManager from '../outputManager.js';
import RESTClient from '../restClient.js';
import RunSpecConfig from '../runSpecConfig.js';
import StatusManager from '../statusManager.js';
export default class ScriptPublisher {
    cssClient;
    existingScriptId;
    scriptName;
    scriptPath;
    constructor(scriptName, scriptPath) {
        this.cssClient = new CSSClient();
        this.scriptName = scriptName;
        this.scriptPath = scriptPath;
    }
    async hasExistingInstance() {
        return Boolean(await this.getExistingScriptId());
    }
    async publish(packagePath) {
        let scriptId;
        StatusManager.startTask('Synchronizing script configuration');
        const existingScriptId = await this.getExistingScriptId();
        if (existingScriptId === null) {
            OutputManager.detail(`Creating script "${this.scriptName}"`);
            const script = await this.cssClient.post('/scripts', { name: this.scriptName });
            scriptId = script.id;
            OutputManager.detail(`Script created as ${scriptId}`);
        }
        else {
            scriptId = existingScriptId;
            OutputManager.detail(`Updating existing instance of script "${this.scriptName}" (${scriptId})`);
        }
        StatusManager.startTask('Synchronizing packages');
        await this.syncPackages(scriptId, packagePath);
        StatusManager.startTask('Synchronizing run specs');
        await this.syncRunSpecs(scriptId);
    }
    async syncPackages(scriptId, packagePath) {
        const pkg = await this.cssClient.post('/packages', { scriptId });
        const packageId = pkg.id;
        const uploadURL = pkg.url;
        const fileBuffer = fs.readFileSync(packagePath);
        const restClient = new RESTClient(uploadURL);
        await restClient.put(undefined, fileBuffer, undefined, { 'x-ms-blob-type': 'BlockBlob' });
        OutputManager.detail(`Package pushed as ${packageId}`);
        OutputManager.detail(`Updating current package for script to ${packageId}`);
        await this.cssClient.put(`/scripts/${scriptId}`, { currentPackageId: packageId });
        let packagesToPurge = (await this.cssClient.get('/packages', { scriptId }));
        if (packageId) {
            packagesToPurge = packagesToPurge.filter((pkg) => pkg.id !== packageId) || [];
        }
        for (const pkg of packagesToPurge) {
            OutputManager.detail(`Purging obsolete package ${pkg.id}`);
            // eslint-disable-next-line no-await-in-loop
            await this.cssClient.delete(`/packages/${pkg.id}`);
        }
    }
    async getExistingScriptId() {
        if (this.existingScriptId === undefined) {
            const scripts = await new CSSClient().get('/scripts', { name: this.scriptName });
            this.existingScriptId = scripts?.[0]?.id || null;
        }
        return this.existingScriptId;
    }
    async syncRunSpecs(scriptId) {
        const runSpecsOnServer = (await this.cssClient.get('/runSpecs', { scriptId }))
            // eslint-disable-next-line unicorn/no-array-reduce
            .reduce((acc, rs) => {
            acc[rs.name] = rs;
            return acc;
        }, {});
        const availableImages = await this.cssClient.get('/images');
        const runSpecs = RunSpecConfig.getAll(this.scriptPath);
        for (const runSpec of runSpecs) {
            const parsedRunSpec = runSpec.parse();
            const image = availableImages?.find((img) => img.name === parsedRunSpec.image);
            if (!image) {
                throw new Error(`Image with name "${parsedRunSpec.image}" for run spec "${runSpec.getName()}" not found.`);
            }
            const runSpecData = {
                command: parsedRunSpec.command,
                hasEgress: parsedRunSpec.egress,
                imageId: image.id,
                mountPath: parsedRunSpec.scriptDir || constants.DEFAULT_SCRIPT_WORK_DIR,
                pwd: parsedRunSpec.workDir || parsedRunSpec.scriptDir || constants.DEFAULT_SCRIPT_WORK_DIR,
                runVars: this.transformEnvVars(parsedRunSpec.env),
                scriptId
            };
            const runSpecOnServer = runSpecsOnServer[runSpec.getName()];
            if (runSpecOnServer) {
                OutputManager.detail(`Updating run spec "${runSpec.getName()}" (${runSpecOnServer.id})`);
                // eslint-disable-next-line no-await-in-loop
                await this.cssClient.put(`/runSpecs/${runSpecOnServer.id}`, runSpecData);
                delete runSpecsOnServer[runSpec.getName()];
            }
            else {
                OutputManager.detail(`Creating run spec "${runSpec.getName()}"`);
                // eslint-disable-next-line no-await-in-loop
                const newRunSpecOnServer = await this.cssClient.post('/runSpecs', { ...runSpecData, name: runSpec.getName() });
                OutputManager.detail(`Created as ${newRunSpecOnServer.id}`);
            }
        }
        for (const rcName of Object.keys(runSpecsOnServer)) {
            const { id } = runSpecsOnServer[rcName];
            OutputManager.detail(`Deleting old run spec ${rcName} (${id})`);
            // eslint-disable-next-line no-await-in-loop
            await this.cssClient.delete(`/runSpecs/${id}`);
            delete runSpecsOnServer[rcName];
        }
    }
    transformEnvVars(env) {
        return env ? Object.entries(env).map(([name, value]) => ({ name, value })) : undefined;
    }
}
