import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
import { join } from "node:path";
//#region extensions/crypto/src/services/policy-store.ts
/**
* Policy Store — File-based CRUD persistence for policies and usage tracking.
*
* Layout:
*   ~/.openclawnch/policies/<userId>/
*     policies.json   — array of Policy objects
*     usage.json      — array of PolicyUsage (rolling window, pruned on load)
*/
const BASE_DIR = join(process.env.HOME ?? "/home/openclawnch", ".openclawnch", "policies");
/** Max usage entries per policy before pruning (keep 30 days). */
const MAX_USAGE_AGE_MS = 720 * 60 * 60 * 1e3;
/** Maximum policies per user (DoS prevention). */
const MAX_POLICIES_PER_USER = 50;
/** File permissions for policy files (owner read/write only). */
const FILE_MODE = 384;
/**
* Hash userId to prevent collisions: "user@1" and "user_1" must NOT
* map to the same directory. SHA-256 prefix is collision-resistant.
*/
function sanitizeUserId(id) {
	return createHash("sha256").update(id).digest("hex").slice(0, 16);
}
function userDir(userId) {
	const dir = join(BASE_DIR, sanitizeUserId(userId));
	if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
	return dir;
}
function policiesPath(userId) {
	return join(userDir(userId), "policies.json");
}
function usagePath(userId) {
	return join(userDir(userId), "usage.json");
}
var PolicyStore = class {
	cache = /* @__PURE__ */ new Map();
	usageCache = /* @__PURE__ */ new Map();
	/** Set of userIds whose policy files are corrupted. Fail-closed: block all. */
	_corrupted = /* @__PURE__ */ new Set();
	/** Check if the store is corrupted for a user (fail-closed). */
	isCorrupted(userId) {
		return this._corrupted.has(userId);
	}
	/** Find the first userId directory that has policies (for command fallback). */
	findFirstUserWithPolicies() {
		try {
			return (existsSync(BASE_DIR) ? readdirSync(BASE_DIR).filter((d) => {
				return existsSync(join(BASE_DIR, d, "policies.json"));
			}) : [])[0] ?? null;
		} catch {
			return null;
		}
	}
	/** Load all policies for a user. */
	listPolicies(userId) {
		const cached = this.cache.get(userId);
		if (cached) return cached;
		const path = policiesPath(userId);
		if (!existsSync(path)) return [];
		try {
			const data = JSON.parse(readFileSync(path, "utf8"));
			this.cache.set(userId, data);
			return data;
		} catch (err) {
			this._corrupted.add(userId);
			try {
				renameSync(path, path + ".corrupt." + Date.now());
			} catch {}
			return [];
		}
	}
	/** Get active policies only. */
	getActivePolicies(userId) {
		return this.listPolicies(userId).filter((p) => p.status === "active");
	}
	/** Get a single policy by ID. */
	getPolicy(userId, policyId) {
		return this.listPolicies(userId).find((p) => p.id === policyId);
	}
	/** Get a single policy by name (case-insensitive). */
	getPolicyByName(userId, name) {
		const lower = name.toLowerCase();
		return this.listPolicies(userId).find((p) => p.name.toLowerCase() === lower);
	}
	/** Create or update a policy. Throws if max count exceeded on insert. */
	savePolicy(policy) {
		const policies = this.listPolicies(policy.userId);
		const idx = policies.findIndex((p) => p.id === policy.id);
		if (idx >= 0) policies[idx] = policy;
		else {
			if (policies.length >= MAX_POLICIES_PER_USER) throw new Error(`Maximum ${MAX_POLICIES_PER_USER} policies per user reached. Delete some before creating new ones.`);
			policies.push(policy);
		}
		this.cache.set(policy.userId, policies);
		this.persist(policy.userId);
	}
	/** Delete a policy by ID. */
	deletePolicy(userId, policyId) {
		const policies = this.listPolicies(userId);
		const idx = policies.findIndex((p) => p.id === policyId);
		if (idx < 0) return false;
		policies.splice(idx, 1);
		this.cache.set(userId, policies);
		this.persist(userId);
		const usages = this.loadUsage(userId);
		const uIdx = usages.findIndex((u) => u.policyId === policyId);
		if (uIdx >= 0) {
			usages.splice(uIdx, 1);
			this.usageCache.set(userId, usages);
			this.persistUsage(userId);
		}
		return true;
	}
	/** Load usage data for a user, pruning stale entries. */
	loadUsage(userId) {
		const cached = this.usageCache.get(userId);
		if (cached) return cached;
		const path = usagePath(userId);
		if (!existsSync(path)) return [];
		try {
			const data = JSON.parse(readFileSync(path, "utf8"));
			const cutoff = Date.now() - MAX_USAGE_AGE_MS;
			let pruned = false;
			for (const pu of data) {
				const before = pu.entries.length;
				pu.entries = pu.entries.filter((e) => e.timestamp > cutoff);
				if (pu.entries.length < before) pruned = true;
			}
			this.usageCache.set(userId, data);
			if (pruned) this.persistUsage(userId);
			return data;
		} catch {
			return [];
		}
	}
	/** Get usage for a specific policy. */
	getUsage(userId, policyId) {
		return this.loadUsage(userId).find((u) => u.policyId === policyId);
	}
	/** Record a usage entry for a policy. */
	recordUsage(userId, policyId, entry) {
		const usages = this.loadUsage(userId);
		let pu = usages.find((u) => u.policyId === policyId);
		if (!pu) {
			pu = {
				policyId,
				entries: []
			};
			usages.push(pu);
		}
		pu.entries.push(entry);
		this.usageCache.set(userId, usages);
		this.persistUsage(userId);
	}
	/** Get total spend in a time window for a policy. */
	getSpendInWindow(userId, policyId, windowMs) {
		const pu = this.getUsage(userId, policyId);
		if (!pu) return 0;
		const cutoff = Date.now() - windowMs;
		return pu.entries.filter((e) => e.timestamp > cutoff && e.amountUsd != null).reduce((sum, e) => sum + (e.amountUsd ?? 0), 0);
	}
	/** Get call count in a time window for a policy. */
	getCallsInWindow(userId, policyId, windowMs) {
		const pu = this.getUsage(userId, policyId);
		if (!pu) return 0;
		const cutoff = Date.now() - windowMs;
		return pu.entries.filter((e) => e.timestamp > cutoff).length;
	}
	persist(userId) {
		const policies = this.cache.get(userId) ?? [];
		atomicWrite(policiesPath(userId), JSON.stringify(policies, null, 2));
	}
	persistUsage(userId) {
		const usages = this.usageCache.get(userId) ?? [];
		atomicWrite(usagePath(userId), JSON.stringify(usages, null, 2));
	}
	/** Clear all caches (for testing). */
	reset() {
		this.cache.clear();
		this.usageCache.clear();
		this._corrupted.clear();
	}
};
/**
* Write to a temp file then rename (atomic on POSIX).
* Prevents data loss on crash mid-write. Sets restrictive permissions.
*/
function atomicWrite(targetPath, data) {
	const tmpPath = targetPath + ".tmp." + Date.now();
	writeFileSync(tmpPath, data, { mode: FILE_MODE });
	renameSync(tmpPath, targetPath);
}
let _instance = null;
function getPolicyStore() {
	if (!_instance) _instance = new PolicyStore();
	return _instance;
}
function resetPolicyStore() {
	_instance?.reset();
	_instance = null;
}
//#endregion
export { PolicyStore, getPolicyStore, resetPolicyStore };

//# sourceMappingURL=policy-store.mjs.map