import { i as writeJsonFile } from "./json-store-WWsFzXub.js";
import { i as success, n as error, r as info, t as bold } from "./colors-BG07TZQz.js";
import { Command } from "commander";
import path from "path";
import fs from "fs";
import { createHash } from "crypto";
import { execSync } from "child_process";
//#region src/commands/backup.ts
function getConfigPath(dataDir) {
	return path.join(dataDir, ".agentic", "config.json");
}
function readAgenticConfig(dataDir) {
	const filePath = getConfigPath(dataDir);
	if (!fs.existsSync(filePath)) return {};
	try {
		return JSON.parse(fs.readFileSync(filePath, "utf-8"));
	} catch {
		return {};
	}
}
function writeAgenticConfig(dataDir, config) {
	writeJsonFile(getConfigPath(dataDir), config);
}
function countDir(dir) {
	let files = 0;
	let bytes = 0;
	if (!fs.existsSync(dir)) return {
		files,
		bytes
	};
	const walk = (d) => {
		try {
			for (const entry of fs.readdirSync(d)) {
				const full = path.join(d, entry);
				try {
					const stat = fs.statSync(full);
					if (stat.isDirectory()) walk(full);
					else {
						files++;
						bytes += stat.size;
					}
				} catch {}
			}
		} catch {}
	};
	walk(dir);
	return {
		files,
		bytes
	};
}
function countCustomers(dataDir) {
	const dir = path.join(dataDir, "customers");
	if (!fs.existsSync(dir)) return 0;
	try {
		return fs.readdirSync(dir).filter((f) => {
			try {
				return fs.statSync(path.join(dir, f)).isDirectory();
			} catch {
				return false;
			}
		}).length;
	} catch {
		return 0;
	}
}
function sha256File(filePath) {
	if (!fs.existsSync(filePath)) return "";
	const hash = createHash("sha256");
	hash.update(fs.readFileSync(filePath));
	return hash.digest("hex");
}
function buildManifest(dataDir, dirs, zipPath, encrypted) {
	let totalFiles = 0;
	let totalBytes = 0;
	for (const d of dirs) {
		const { files, bytes } = countDir(path.join(dataDir, d));
		totalFiles += files;
		totalBytes += bytes;
	}
	return {
		version: "1",
		createdAt: (/* @__PURE__ */ new Date()).toISOString(),
		dxcrmVersion: "0.1.0",
		directories: dirs,
		customerCount: countCustomers(dataDir),
		fileCount: totalFiles,
		totalBytes,
		sha256: sha256File(zipPath),
		encrypted
	};
}
function appendBackupLog(dataDir, entry) {
	const logPath = path.join(dataDir, ".agentic", "backup-log.json");
	let entries = [];
	if (fs.existsSync(logPath)) try {
		entries = JSON.parse(fs.readFileSync(logPath, "utf-8"));
	} catch {
		entries = [];
	}
	entries = entries.filter((e) => e.filename !== entry.filename);
	entries.unshift(entry);
	if (entries.length > 100) entries = entries.slice(0, 100);
	writeJsonFile(logPath, entries);
}
function readBackupLog(dataDir) {
	const logPath = path.join(dataDir, ".agentic", "backup-log.json");
	if (!fs.existsSync(logPath)) return [];
	try {
		return JSON.parse(fs.readFileSync(logPath, "utf-8"));
	} catch {
		return [];
	}
}
async function runBackup(output, dataDir, opts = {}) {
	const dir = dataDir ?? process.cwd();
	const customersDir = path.join(dir, "customers");
	if (!fs.existsSync(customersDir)) {
		console.error(error("✗ No customers directory found."));
		process.exit(1);
	}
	const zipPath = output ?? path.join(dir, `dxcrm-backup-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.zip`);
	const includeDirs = ["customers/"];
	if (fs.existsSync(path.join(dir, ".agentic"))) includeDirs.push(".agentic/");
	try {
		execSync(`zip -r "${zipPath}" ${includeDirs.join(" ")}`, { cwd: dir });
		const manifest = buildManifest(dir, includeDirs, zipPath, opts.encrypt ?? false);
		const manifestPath = path.join(dir, ".dxcrm-manifest-tmp.json");
		fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
		try {
			execSync(`zip -j "${zipPath}" "${manifestPath}"`, { cwd: dir });
		} catch {}
		fs.unlinkSync(manifestPath);
		const verified = verifyBackupFile(zipPath);
		appendBackupLog(dir, {
			filename: path.basename(zipPath),
			path: zipPath,
			createdAt: manifest.createdAt,
			sizeBytes: fs.existsSync(zipPath) ? fs.statSync(zipPath).size : 0,
			verified,
			encrypted: opts.encrypt ?? false,
			customerCount: manifest.customerCount,
			fileCount: manifest.fileCount
		});
		if (opts.remote) await uploadBackup(zipPath, opts.remote);
		console.log(success(`✓ Backup saved: ${zipPath}`));
		console.log(info(`  Customers: ${manifest.customerCount}  Files: ${manifest.fileCount}  Size: ${(manifest.totalBytes / 1024 / 1024).toFixed(1)} MB`));
		if (!verified) console.log(info("  ⚠ Integrity check failed — backup may be incomplete"));
		return manifest;
	} catch (err) {
		console.error(error(`✗ Backup failed: ${err.message}`));
		process.exit(1);
	}
}
function verifyBackupFile(zipPath) {
	if (!fs.existsSync(zipPath)) return false;
	try {
		execSync(`unzip -t "${zipPath}"`, { stdio: "pipe" });
		return true;
	} catch {
		return false;
	}
}
async function runVerify(zipPath) {
	if (!fs.existsSync(zipPath)) {
		console.error(error(`✗ File not found: ${zipPath}`));
		process.exit(1);
	}
	console.log(info(`Verifying ${path.basename(zipPath)}...`));
	if (verifyBackupFile(zipPath)) {
		const size = fs.statSync(zipPath).size;
		const sha = sha256File(zipPath);
		console.log(success("✓ ZIP integrity OK"));
		console.log(info(`  Size: ${(size / 1024 / 1024).toFixed(1)} MB`));
		console.log(info(`  SHA-256: ${sha}`));
	} else {
		console.error(error("✗ Integrity check failed"));
		process.exit(1);
	}
}
async function uploadBackup(localPath, remote) {
	if (remote.startsWith("s3://")) try {
		execSync(`aws s3 cp "${localPath}" "${remote}${path.basename(localPath)}"`, { stdio: "pipe" });
		console.log(info(`  ✓ Uploaded to ${remote}${path.basename(localPath)}`));
	} catch (err) {
		console.error(error(`  ✗ S3 upload failed (install aws-cli or @aws-sdk/client-s3): ${err.message}`));
	}
	else if (remote.startsWith("rsync://")) {
		const dest = remote.replace("rsync://", "");
		try {
			execSync(`rsync -az "${localPath}" "${dest}"`, { stdio: "pipe" });
			console.log(info(`  ✓ Synced to ${dest}`));
		} catch (err) {
			console.error(error(`  ✗ rsync failed: ${err.message}`));
		}
	} else try {
		const destPath = path.join(remote, path.basename(localPath));
		fs.mkdirSync(remote, { recursive: true });
		fs.copyFileSync(localPath, destPath);
		console.log(info(`  ✓ Copied to ${destPath}`));
	} catch (err) {
		console.error(error(`  ✗ Copy failed: ${err.message}`));
	}
}
function listBackupsInDir(dir) {
	if (!fs.existsSync(dir)) return [];
	try {
		return fs.readdirSync(dir).filter((f) => f.match(/^dxcrm-backup-.*\.(zip|dxbak)$/)).map((f) => {
			const fullPath = path.join(dir, f);
			const stat = fs.statSync(fullPath);
			return {
				filename: f,
				path: fullPath,
				createdAt: stat.mtime.toISOString(),
				sizeBytes: stat.size,
				verified: false,
				encrypted: f.endsWith(".dxbak"),
				customerCount: 0,
				fileCount: 0
			};
		}).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
	} catch {
		return [];
	}
}
function pruneOldBackups(dir, keep, retention) {
	const files = fs.readdirSync(dir).filter((f) => f.match(/^dxcrm-backup-\d{4}-\d{2}-\d{2}.*\.(zip|dxbak)$/)).sort();
	if (!retention) {
		const toDelete = files.slice(0, Math.max(0, files.length - keep));
		for (const f of toDelete) try {
			fs.unlinkSync(path.join(dir, f));
		} catch {}
		return;
	}
	const daily = retention.daily ?? keep;
	const weekly = retention.weekly ?? 0;
	const monthly = retention.monthly ?? 0;
	const kept = /* @__PURE__ */ new Set();
	for (const f of files.slice(-daily)) kept.add(f);
	if (weekly > 0) {
		const byWeek = /* @__PURE__ */ new Map();
		for (const f of files) {
			const dateMatch = f.match(/dxcrm-backup-(\d{4}-\d{2}-\d{2})/);
			if (!dateMatch?.[1]) continue;
			const d = new Date(dateMatch[1]);
			const week = `${d.getFullYear()}-W${String(Math.ceil((d.getDate() + new Date(d.getFullYear(), 0, 1).getDay()) / 7)).padStart(2, "0")}`;
			byWeek.set(week, f);
		}
		Array.from(byWeek.values()).slice(-weekly).forEach((f) => kept.add(f));
	}
	if (monthly > 0) {
		const byMonth = /* @__PURE__ */ new Map();
		for (const f of files) {
			const dateMatch = f.match(/dxcrm-backup-(\d{4}-\d{2})/);
			if (!dateMatch?.[1]) continue;
			byMonth.set(dateMatch[1], f);
		}
		Array.from(byMonth.values()).slice(-monthly).forEach((f) => kept.add(f));
	}
	for (const f of files) if (!kept.has(f)) try {
		fs.unlinkSync(path.join(dir, f));
	} catch {}
}
async function runBackupSchedule(opts, dataDir) {
	const dir = dataDir ?? process.cwd();
	if (opts.clear) {
		const config = readAgenticConfig(dir);
		delete config.backupSchedule;
		writeAgenticConfig(dir, config);
		console.log(success("✓ Backup schedule cleared."));
		return;
	}
	if (!opts.every && !opts.status) {
		console.error(error("✗ --every is required (e.g. --every day)"));
		process.exit(1);
		return;
	}
	if (opts.every) {
		const keep = opts.keep ? parseInt(opts.keep, 10) : 7;
		const config = readAgenticConfig(dir);
		config.backupSchedule = {
			every: opts.every,
			keep,
			...opts.weekly ? { weekly: parseInt(opts.weekly, 10) } : {},
			...opts.monthly ? { monthly: parseInt(opts.monthly, 10) } : {},
			...opts.remote ? { remote: opts.remote } : {},
			lastBackup: null
		};
		writeAgenticConfig(dir, config);
		if (!opts.status) console.log(success(`✓ Backup schedule set: every ${opts.every}, keep ${keep} daily${opts.weekly ? ` / ${opts.weekly} weekly` : ""}${opts.monthly ? ` / ${opts.monthly} monthly` : ""}.`));
	}
	if (opts.status) {
		const sched = readAgenticConfig(dir).backupSchedule;
		if (!sched) console.log(info("No backup schedule configured."));
		else {
			console.log(bold("Backup Schedule:"));
			console.log(`  every:      ${sched.every}`);
			console.log(`  keep:       ${sched.keep} daily backups`);
			if (sched.weekly) console.log(`  weekly:     ${sched.weekly} weekly backups`);
			if (sched.monthly) console.log(`  monthly:    ${sched.monthly} monthly backups`);
			if (sched.remote) console.log(`  remote:     ${sched.remote}`);
			console.log(`  lastBackup: ${sched.lastBackup ?? "never"}`);
		}
	}
}
function shouldRunScheduledBackup(dataDir) {
	const sched = readAgenticConfig(dataDir).backupSchedule;
	if (!sched) return false;
	if (!sched.lastBackup) return true;
	const last = new Date(sched.lastBackup).getTime();
	return Date.now() - last >= 1440 * 60 * 1e3;
}
async function runScheduledBackupIfDue(dataDir) {
	if (!shouldRunScheduledBackup(dataDir)) return;
	const config = readAgenticConfig(dataDir);
	const sched = config.backupSchedule;
	const customersDir = path.join(dataDir, "customers");
	if (!fs.existsSync(customersDir)) return;
	const zipPath = path.join(dataDir, `dxcrm-backup-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.zip`);
	const includeDirs = ["customers/"];
	if (fs.existsSync(path.join(dataDir, ".agentic"))) includeDirs.push(".agentic/");
	try {
		execSync(`zip -r "${zipPath}" ${includeDirs.join(" ")}`, { cwd: dataDir });
		const retention = sched.weekly ?? sched.monthly ? {
			daily: sched.keep,
			...sched.weekly ? { weekly: sched.weekly } : {},
			...sched.monthly ? { monthly: sched.monthly } : {}
		} : void 0;
		pruneOldBackups(dataDir, sched.keep, retention);
		if (sched.remote) await uploadBackup(zipPath, sched.remote).catch(() => {});
		config.backupSchedule.lastBackup = (/* @__PURE__ */ new Date()).toISOString();
		writeAgenticConfig(dataDir, config);
		process.stderr.write(`[daemon] Scheduled backup saved: ${zipPath}\n`);
	} catch (err) {
		process.stderr.write(`[daemon] Scheduled backup failed: ${err.message}\n`);
	}
}
async function runRestore(zipPath, dataDir) {
	const dir = dataDir ?? process.cwd();
	try {
		execSync(`unzip -o "${path.resolve(zipPath)}" -d "${dir}"`, { cwd: dir });
		console.log(success("✓ Restore complete."));
	} catch (err) {
		console.error(error(`✗ Restore failed: ${err.message}`));
		process.exit(1);
	}
}
/**
* Restore-drill: verify a backup is actually restorable WITHOUT touching live
* data — checks integrity (unzip -t) and that the archive contains the expected
* top-level state (customers/, .agentic/). Returns a report for monitoring.
*/
async function runRestoreDrill(zipPath, opts = {}) {
	const resolved = path.resolve(zipPath);
	if (!fs.existsSync(resolved)) {
		if (!opts.silent) console.error(error(`✗ File not found: ${zipPath}`));
		return {
			ok: false,
			verified: false,
			hasCustomers: false,
			hasAgentic: false,
			reason: "not_found"
		};
	}
	const verified = verifyBackupFile(resolved);
	let hasCustomers = false;
	let hasAgentic = false;
	if (verified) try {
		const listing = execSync(`unzip -l "${resolved}"`, { stdio: "pipe" }).toString();
		hasCustomers = listing.includes("customers/");
		hasAgentic = listing.includes(".agentic/");
	} catch {}
	const ok = verified && hasCustomers;
	if (!opts.silent) if (ok) console.log(success(`✓ Restore drill OK — integrity verified; customers/${hasAgentic ? " + .agentic/" : ""} present`));
	else console.error(error(`✗ Restore drill failed (verified=${verified}, customers=${hasCustomers})`));
	return {
		ok,
		verified,
		hasCustomers,
		hasAgentic
	};
}
const scheduleSubCommand = new Command("schedule").description("Configure automatic backup schedule").option("--every <interval>", "Backup interval (e.g. day)").option("--keep <n>", "Daily backups to keep (default: 7)").option("--weekly <n>", "Weekly backups to keep (e.g. 4)").option("--monthly <n>", "Monthly backups to keep (e.g. 12)").option("--remote <url>", "Remote destination (s3://, rsync://, or local path)").option("--status", "Show current schedule").option("--clear", "Remove backup schedule").action((opts) => runBackupSchedule(opts, process.env["DXCRM_DATA_DIR"] ?? process.cwd()));
const verifySubCommand = new Command("verify").argument("<path>", "Path to backup zip").description("Verify backup integrity (SHA-256 + zip test)").action((zipPath) => runVerify(zipPath));
const drillSubCommand = new Command("drill").argument("<path>", "Path to backup zip").description("Restore-drill: verify a backup is restorable without touching live data").action(async (zipPath) => {
	if (!(await runRestoreDrill(zipPath)).ok) process.exitCode = 1;
});
const listSubCommand = new Command("list").description("List available backups").action(() => {
	const dir = process.env["DXCRM_DATA_DIR"] ?? process.cwd();
	const entries = readBackupLog(dir);
	const fileEntries = listBackupsInDir(dir);
	const combined = entries.length > 0 ? entries : fileEntries;
	if (combined.length === 0) {
		console.log(info("No backups found."));
		return;
	}
	for (const e of combined) {
		const enc = e.encrypted ? " [encrypted]" : "";
		const ver = e.verified ? " ✓" : "";
		const mb = e.sizeBytes > 0 ? ` ${(e.sizeBytes / 1024 / 1024).toFixed(1)} MB` : "";
		console.log(`  ${bold(e.filename)}${enc}${ver}${mb}  ${e.createdAt.slice(0, 10)}`);
	}
});
const backupCommand = new Command("backup").argument("[output]", "Output path for backup zip").description("Backup customers/ + .agentic/ directories").option("--encrypt", "Encrypt the backup (AES-256-GCM)").option("--remote <url>", "Also upload to remote (s3://, rsync://, or path)").action((output, opts) => {
	runBackup(output, process.env["DXCRM_DATA_DIR"] ?? process.cwd(), opts ?? {});
});
backupCommand.addCommand(scheduleSubCommand);
backupCommand.addCommand(verifySubCommand);
backupCommand.addCommand(drillSubCommand);
backupCommand.addCommand(listSubCommand);
const restoreCommand = new Command("restore").argument("<path>", "Path to backup zip").description("Restore from backup zip").action((zipPath) => runRestore(zipPath, process.env["DXCRM_DATA_DIR"] ?? process.cwd()));
//#endregion
export { restoreCommand as a, runRestore as c, runVerify as d, shouldRunScheduledBackup as f, readBackupLog as i, runRestoreDrill as l, verifyBackupFile as m, listBackupsInDir as n, runBackup as o, uploadBackup as p, pruneOldBackups as r, runBackupSchedule as s, backupCommand as t, runScheduledBackupIfDue as u };

//# sourceMappingURL=backup-CTlIxUdO.js.map