import { cp, mkdir, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getLogger } from "../../../../util/logger.js";
const SB_WORKTREE_SNAPSHOTS_DIR = join(tmpdir(), "sb-worktree-snapshots");
const BUILD_POLL_INTERVAL_MS = 5_000;
const BUILD_POLL_TIMEOUT_MS = 5 * 60 * 1_000;
const TERMINAL_BUILD_STATUSES = new Set([
    "SUCCESS",
    "BUILD_STATUS_SUCCESS",
    "FAILED",
    "BUILD_STATUS_FAILED",
    "TIMED_OUT",
    "BUILD_STATUS_TIMED_OUT",
]);
function isSuccessStatus(status) {
    return status === "SUCCESS" || status === "BUILD_STATUS_SUCCESS";
}
function isFailedStatus(status) {
    return status === "FAILED" || status === "BUILD_STATUS_FAILED";
}
function isTimedOutStatus(status) {
    return status === "TIMED_OUT" || status === "BUILD_STATUS_TIMED_OUT";
}
async function serverPost(superblocksBaseUrl, jwt, path, body) {
    const url = `${superblocksBaseUrl}/api/${path}`;
    const response = await fetch(url, {
        method: "POST",
        headers: {
            Authorization: `Bearer ${jwt}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify(body),
    });
    if (!response.ok) {
        const text = await response.text().catch(() => "");
        throw new Error(`Deploy API call to ${path} failed (${response.status}): ${text}`);
    }
    return (await response.json());
}
export function createDeployService(deps) {
    const { applicationId, gitService, getJwt, snapshotUploader, superblocksBaseUrl, } = deps;
    const logger = getLogger();
    function requireJwt() {
        const jwt = getJwt();
        if (!jwt) {
            throw new Error("No authentication token available for deploy");
        }
        return jwt;
    }
    return {
        async prepareMainCommit(sha) {
            const worktreeBase = await mkdtemp(join(tmpdir(), "sb-main-deploy-"));
            const worktreePath = join(worktreeBase, "worktree");
            try {
                await gitService.raw([
                    "worktree",
                    "add",
                    "--detach",
                    worktreePath,
                    sha,
                ]);
                await rm(join(worktreePath, ".git"), {
                    force: true,
                    recursive: true,
                });
                const { directoryHash } = await snapshotUploader.uploadSnapshotToDbfs(worktreePath);
                const snapshotPath = join(SB_WORKTREE_SNAPSHOTS_DIR, encodeURIComponent(directoryHash));
                await mkdir(SB_WORKTREE_SNAPSHOTS_DIR, { recursive: true });
                await rm(snapshotPath, { recursive: true, force: true });
                await cp(worktreePath, snapshotPath, { recursive: true });
                return { directoryHash, sha };
            }
            finally {
                try {
                    await gitService.raw(["worktree", "remove", "--force", worktreePath]);
                }
                catch {
                    try {
                        await gitService.raw(["worktree", "prune"]);
                    }
                    catch (pruneErr) {
                        logger.warn("[deploy-service] worktree prune failed", {
                            worktreePath,
                            error: pruneErr instanceof Error ? pruneErr.message : String(pruneErr),
                        });
                    }
                }
                await rm(worktreeBase, { recursive: true, force: true });
            }
        },
        async syncMainToDbfs(directoryHash, sha) {
            const jwt = requireJwt();
            const result = await serverPost(superblocksBaseUrl, jwt, `v3/applications/${applicationId}/sync-main-to-dbfs`, { directoryHash, sha });
            const commitId = result.data?.commitId;
            if (!commitId) {
                throw new Error("Server did not return a commitId");
            }
            return { commitId };
        },
        async createBuild(directoryHash, commitId) {
            const jwt = requireJwt();
            await serverPost(superblocksBaseUrl, jwt, `v3/applications/${applicationId}/${encodeURIComponent(directoryHash)}/builds`, { commitId });
        },
        async pollBuildUntilComplete(directoryHash) {
            const startTime = Date.now();
            while (Date.now() - startTime < BUILD_POLL_TIMEOUT_MS) {
                const jwt = requireJwt();
                const result = await serverPost(superblocksBaseUrl, jwt, `v3/applications/${applicationId}/build-bulk-status`, { directoryHashes: [directoryHash] });
                const entry = result.data?.directoryHashToStatus?.[directoryHash];
                if (entry && TERMINAL_BUILD_STATUSES.has(entry.status)) {
                    if (isSuccessStatus(entry.status)) {
                        return { succeeded: true };
                    }
                    if (isFailedStatus(entry.status)) {
                        return { succeeded: false, error: "Build failed" };
                    }
                    if (isTimedOutStatus(entry.status)) {
                        return { succeeded: false, error: "Build timed out" };
                    }
                }
                await new Promise((resolve) => setTimeout(resolve, BUILD_POLL_INTERVAL_MS));
            }
            return {
                succeeded: false,
                error: "Build polling timed out after 5 minutes",
            };
        },
        async deployCommit(commitId, deployMessage) {
            const jwt = requireJwt();
            await serverPost(superblocksBaseUrl, jwt, `v2/applications/${applicationId}/deployments`, { commitId, deployMessage });
        },
    };
}
//# sourceMappingURL=deploy-service.js.map