import { randomBytes } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
import { tmpdir } from "node:os";
import { join, relative, sep } from "node:path";
import ignore from "ignore";
import { minimatch } from "minimatch";
import { simpleGit } from "simple-git";
import * as tar from "tar";
import { DEPLOYER_METADATA_FILE } from "../common/constants.js";
import { logger } from "../common/logger.js";
export const ARCHIVE_EXTENSION = ".tar.gz";
function createTempFileWithSuffix(suffix = ARCHIVE_EXTENSION) {
    const tempDir = tmpdir();
    const randomName = randomBytes(16).toString("hex");
    const tempFilePath = join(tempDir, `${randomName}${suffix}`);
    return tempFilePath;
}
export async function archive(argv) {
    const tarball = createTempFileWithSuffix();
    const dir = argv.dir;
    const ignoreFn = createIgnoreFunction(dir);
    const normalizedDir = join(relative(process.cwd(), dir));
    const metadata = await prepareDeployerMetadata(argv);
    await tar.create({
        gzip: true,
        file: tarball,
        filter: (path, stat) => {
            const stats = stat;
            const normalizedPath = stats.isDirectory()
                ? join(relative(process.cwd(), path), sep)
                : join(relative(process.cwd(), path));
            if (minimatch(normalizedPath, "/", { windowsPathsNoEscape: true })) {
                return true;
            }
            if (minimatch(normalizedPath, ".zuplo/", { windowsPathsNoEscape: true })) {
                return true;
            }
            if (minimatch(normalizedPath, `.zuplo/${DEPLOYER_METADATA_FILE}`, {
                windowsPathsNoEscape: true,
            })) {
                return true;
            }
            if (normalizedPath === ".env" || normalizedPath === ".env.zuplo") {
                if (argv["self-hosted-endpoint"]) {
                    return true;
                }
            }
            const result = ignoreFn.ignores(normalizedPath);
            logger.trace(`${normalizedPath} ignored: ${result}`);
            return !result;
        },
    }, [normalizedDir]);
    return {
        tarball,
        metadata,
    };
}
export function createIgnoreFunction(normalizedDir) {
    const baseIgnore = ignore().add(".git/");
    if (existsSync(`${normalizedDir}/.zupignore`)) {
        const zupignorePath = `${normalizedDir}/.zupignore`;
        logger.debug(`Using ${zupignorePath} to filter out files`);
        return baseIgnore.add(readFileSync(zupignorePath).toString());
    }
    else if (existsSync(`${normalizedDir}/.gitignore`)) {
        const gitignorePath = `${normalizedDir}/.gitignore`;
        logger.debug(`Using ${gitignorePath} to filter out files`);
        const gitIgnoreContent = readFileSync(gitignorePath).toString();
        const gitIgnoreContentDotZuploRemoved = gitIgnoreContent.replace(/\.zuplo[/]/, "");
        return baseIgnore.add(gitIgnoreContentDotZuploRemoved);
    }
    else {
        logger.debug("Didn't find a .gitignore or .zupignore file. Defaulting to ignoring .git and node_modules.");
        return baseIgnore.add("node_modules/");
    }
}
async function prepareDeployerMetadata(argv) {
    const dir = argv.dir;
    const metadata = await generateMetadata(argv);
    await writeGeneratedMetadata(dir, metadata);
    return metadata;
}
export async function generateMetadata(argv) {
    const dir = argv.dir;
    const git = simpleGit({ baseDir: dir });
    const isRepo = await git.checkIsRepo();
    let branch;
    if (argv.environment) {
        branch = argv.environment;
    }
    else if (isRepo) {
        const status = await git.status();
        if (!status.current) {
            throw new Error("Invalid state: Directory is in detached head state.");
        }
        branch = status.current.trim();
        if (branch === "HEAD") {
            await git.fetch(["--all"]);
            const branchCommit = await git.branch(["-a"]);
            const brancheRef = await git.branch([
                "-r",
                "--contains",
                branchCommit.current,
            ]);
            if (brancheRef && brancheRef.all && brancheRef.all[0]) {
                const originBranch = brancheRef.all[0];
                const cleanOriginBranch = originBranch.replace(/^origin\//, "");
                if (!cleanOriginBranch) {
                    throw new Error("Invalid state: Directory is in detached head state.");
                }
                branch = cleanOriginBranch.trim();
            }
        }
    }
    else {
        throw new Error("The argument `environment` is required when not in a git repository.");
    }
    let sha;
    let repoUrl;
    if (argv["override-repo-url"]) {
        repoUrl = argv["override-repo-url"];
    }
    else if (isRepo) {
        sha = (await git.revparse(["HEAD"])).trim();
        try {
            repoUrl = (await git.listRemote(["--get-url"])).trim();
        }
        catch {
            logger.debug("Failed to get the remote URL from the git repository. This can happen if there is no remote configured.");
            repoUrl = undefined;
        }
    }
    return {
        branch,
        repoUrl,
        sha,
    };
}
async function writeGeneratedMetadata(dir, metadata) {
    try {
        mkdirSync(join(dir, ".zuplo"));
    }
    catch (err) {
        if (err.code === "EEXIST") {
        }
        else {
            throw err;
        }
    }
    writeFileSync(join(dir, ".zuplo", DEPLOYER_METADATA_FILE), JSON.stringify(metadata, null, 2), {
        flag: "w",
    });
}
//# sourceMappingURL=archive.js.map