import { execFileSync } from 'node:child_process';
import { chmodSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { messages } from '../../../core/messages/index.js';
const HTTP_REDIRECT = 302;
const { GithubRepoDownloaderMensagens } = messages;
const OWNER_REPO_RE = /^[a-zA-Z0-9_.-]+$/;
const REF_RE = /^[a-zA-Z0-9_.\-/]+$/;
function validarInput(valor, regex, nome) {
    if (!regex.test(valor)) {
        throw new Error(messages.ExcecoesMensagens.inputInvalido(nome));
    }
}
function validarRedirectUrl(url) {
    const parsed = new URL(url);
    if (!parsed.hostname.endsWith('.github.com') && parsed.hostname !== 'github.com') {
        throw new Error(GithubRepoDownloaderMensagens.redirectInvalido);
    }
    if (!parsed.protocol.startsWith('https')) {
        throw new Error(GithubRepoDownloaderMensagens.redirectInvalido);
    }
    return parsed;
}
export async function getInstallationToken(app, installationId) {
    const { data } = await app.octokit.request('POST /app/installations/{installation_id}/access_tokens', {
        installation_id: installationId
    });
    return data.token;
}
export async function downloadRepo(app, installationId, owner, repo, ref) {
    validarInput(owner, OWNER_REPO_RE, 'owner');
    validarInput(repo, OWNER_REPO_RE, 'repo');
    validarInput(ref, REF_RE, 'ref');
    const tempDir = mkdtempSync(join(tmpdir(), 'tutanus-repo-'));
    const zipPath = join(tempDir, 'repo.zip');
    try {
        const octokit = await app.getInstallationOctokit(installationId);
        const response = await octokit.request('GET /repos/{owner}/{repo}/zipball/{ref}', {
            owner,
            repo,
            ref,
            request: { redirect: 'manual' }
        });
        let zipBuffer;
        if (response.status === HTTP_REDIRECT && response.headers.location) {
            const redirectUrl = validarRedirectUrl(response.headers.location);
            const token = await getInstallationToken(app, installationId);
            const zipResponse = await fetch(redirectUrl, {
                headers: { Authorization: `Bearer ${token}` }
            });
            if (!zipResponse.ok) {
                throw new Error(GithubRepoDownloaderMensagens.falhaBaixarZipball(zipResponse.status));
            }
            zipBuffer = Buffer.from(await zipResponse.arrayBuffer());
        }
        else if (Buffer.isBuffer(response.data)) {
            zipBuffer = response.data;
        }
        else {
            throw new Error(GithubRepoDownloaderMensagens.respostaInesperada);
        }
        writeFileSync(zipPath, zipBuffer);
        execFileSync('unzip', ['-q', zipPath, '-d', tempDir], { stdio: 'pipe' });
        rmSync(zipPath);
        validarSemPathTraversal(tempDir);
        const entries = readdirSync(tempDir);
        const repoDirName = entries?.find(e => e !== 'repo.zip') ?? '';
        if (!repoDirName) {
            throw new Error(GithubRepoDownloaderMensagens.nenhumDiretorioEncontrado);
        }
        return {
            tempDir,
            repoDir: join(tempDir, repoDirName),
            cleanup: () => {
                try {
                    rmSync(tempDir, { recursive: true, force: true });
                }
                catch { }
            }
        };
    }
    catch (err) {
        rmSync(tempDir, { recursive: true, force: true });
        throw err;
    }
}
export function escreverAskpassScript() {
    const tmpScript = join(tmpdir(), `tutanus-askpass-${Date.now()}.sh`);
    writeFileSync(tmpScript, '#!/bin/sh\necho "$TUTANUS_GIT_TOKEN"');
    chmodSync(tmpScript, 0o500);
    return tmpScript;
}
export function limparAskpassScript(caminho) {
    try {
        rmSync(caminho);
    }
    catch { }
}
function validarSemPathTraversal(dir) {
    const pilha = [dir];
    while (pilha.length > 0) {
        const atual = pilha.pop();
        if (!atual)
            continue;
        for (const entry of readdirSync(atual)) {
            const caminhoCompleto = join(atual, entry);
            if (!caminhoCompleto.startsWith(dir)) {
                rmSync(dir, { recursive: true, force: true });
                throw new Error(GithubRepoDownloaderMensagens.pathTraversalDetectado);
            }
            if (statSync(caminhoCompleto).isDirectory()) {
                pilha.push(caminhoCompleto);
            }
        }
    }
}
export async function cloneRepo(app, installationId, owner, repo, ref) {
    validarInput(owner, OWNER_REPO_RE, 'owner');
    validarInput(repo, OWNER_REPO_RE, 'repo');
    validarInput(ref, REF_RE, 'ref');
    const tempDir = mkdtempSync(join(tmpdir(), 'tutanus-repo-'));
    const repoDir = join(tempDir, 'repo');
    const token = await getInstallationToken(app, installationId);
    const askpassScript = escreverAskpassScript();
    try {
        execFileSync('git', ['clone', '--depth', '1', '--branch', ref,
            `https://github.com/${owner}/${repo}.git`, repoDir], {
            stdio: 'pipe',
            timeout: 120000,
            env: {
                ...process.env,
                TUTANUS_GIT_TOKEN: token,
                GIT_ASKPASS: askpassScript,
                GIT_TERMINAL_PROMPT: '0'
            }
        });
        return {
            tempDir,
            repoDir,
            cleanup: () => {
                limparAskpassScript(askpassScript);
                try {
                    rmSync(tempDir, { recursive: true, force: true });
                }
                catch { }
            }
        };
    }
    catch (err) {
        limparAskpassScript(askpassScript);
        rmSync(tempDir, { recursive: true, force: true });
        throw err;
    }
}
//# sourceMappingURL=repo-downloader.js.map