/**
 * UOK Chaos Engineering Monkey
 *
 * Purpose: safely stress-test the kernel's durability and parity-recovery
 * mechanisms by injecting controlled, non-fatal faults during critical
 * lifecycle phases. Never kills the process — faults are always recoverable.
 *
 * Consumer: UOK kernel loop adapter and turn observer hooks (opt-in only).
 */

import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { delay } from "../atomic-write.js";
import { getErrorMessage } from "../error-utils.js";

const DEFAULT_LATENCY_PROBABILITY = 0.05;
const DEFAULT_PARTIAL_FAILURE_PROBABILITY = 0.03;
const DEFAULT_DISK_STRESS_PROBABILITY = 0.01;
const DEFAULT_MEMORY_STRESS_PROBABILITY = 0.005;
const DEFAULT_MAX_LATENCY_MS = 5000;
const DEFAULT_DISK_STRESS_MB = 50;
const DEFAULT_MEMORY_STRESS_MB = 100;

function randomInRange(min, max) {
	return min + Math.random() * (max - min);
}

export class ChaosMonkeyGate {
	constructor(options = {}) {
		this.id = "chaos-monkey";
		this.type = "chaos";
		this._monkey = new ChaosMonkey(options);
	}

	async execute(_ctx, attempt) {
		try {
			await this._monkey.strike("verification");
		} catch (err) {
			return {
				outcome: "fail",
				failureClass: "execution",
				rationale: `Chaos monkey injected fault: ${getErrorMessage(err)}`,
				findings: `Injected during verification phase (attempt ${attempt})`,
			};
		}
		const events = this._monkey.getInjectedEvents();
		const last = events[events.length - 1];
		if (last && last.phase === "verification") {
			return {
				outcome: "pass",
				failureClass: "none",
				rationale: `Chaos monkey injected ${last.type} during verification (non-fatal)`,
				findings: `Latency: ${last.delay ?? 0}ms | Disk: ${last.sizeMb ?? 0}MB | Memory: ${last.sizeMb ?? 0}MB`,
			};
		}
		return {
			outcome: "pass",
			failureClass: "none",
			rationale: "Chaos monkey: no fault injected this run",
		};
	}
}

export class ChaosMonkey {
	constructor(options = {}) {
		this.active = options.active ?? false;
		this.latencyProbability =
			options.latencyProbability ?? DEFAULT_LATENCY_PROBABILITY;
		this.partialFailureProbability =
			options.partialFailureProbability ?? DEFAULT_PARTIAL_FAILURE_PROBABILITY;
		this.diskStressProbability =
			options.diskStressProbability ?? DEFAULT_DISK_STRESS_PROBABILITY;
		this.memoryStressProbability =
			options.memoryStressProbability ?? DEFAULT_MEMORY_STRESS_PROBABILITY;
		this.maxLatencyMs = options.maxLatencyMs ?? DEFAULT_MAX_LATENCY_MS;
		this.diskStressMb = options.diskStressMb ?? DEFAULT_DISK_STRESS_MB;
		this.memoryStressMb = options.memoryStressMb ?? DEFAULT_MEMORY_STRESS_MB;
		this._injected = [];
	}

	/**
	 * Inject a fault during the named lifecycle phase.
	 * Returns a promise that resolves after any injected latency.
	 *
	 * @param {string} phase — e.g. "turn-start", "dispatch", "verification", "gitops"
	 */
	async strike(phase) {
		if (!this.active) return;

		// 1. Latency injection
		if (Math.random() < this.latencyProbability) {
			const latencyMs = Math.floor(randomInRange(100, this.maxLatencyMs));
			console.error(
				`[CHAOS MONKEY] Injecting ${latencyMs}ms latency during phase: ${phase}`,
			);
			this._injected.push({ type: "latency", phase, delay: latencyMs });
			await delay(latencyMs);
		}

		// 2. Partial failure (non-fatal error throw)
		if (Math.random() < this.partialFailureProbability) {
			const errors = [
				"Simulated network timeout",
				"Simulated EAGAIN retryable I/O error",
				"Simulated provider rate-limit (429)",
				"Simulated temporary filesystem unavailability",
			];
			const message = errors[Math.floor(Math.random() * errors.length)];
			console.error(
				`[CHAOS MONKEY] Injecting partial failure during phase: ${phase}: ${message}`,
			);
			this._injected.push({ type: "partial-failure", phase, message });
			const err = new Error(message);
			err.code = "CHAOS_INJECTED";
			err.retryable = true;
			throw err;
		}

		// 3. Disk stress (write large temp file, then clean up)
		if (Math.random() < this.diskStressProbability) {
			const sizeMb = Math.floor(randomInRange(1, this.diskStressMb));
			console.error(
				`[CHAOS MONKEY] Injecting disk stress: writing ${sizeMb}MB temp file during phase: ${phase}`,
			);
			this._injected.push({ type: "disk-stress", phase, sizeMb });
			const tmpDir = mkdtempSync(join(tmpdir(), "uok-chaos-"));
			const chunk = Buffer.alloc(1024 * 1024, "x");
			for (let i = 0; i < sizeMb; i++) {
				writeFileSync(join(tmpDir, `stress-${i}.bin`), chunk);
			}
			// Immediate cleanup — we only wanted to stress, not leave garbage
			rmSync(tmpDir, { recursive: true, force: true });
		}

		// 4. Memory stress (allocate large buffer, then free)
		if (Math.random() < this.memoryStressProbability) {
			const sizeMb = Math.floor(randomInRange(10, this.memoryStressMb));
			console.error(
				`[CHAOS MONKEY] Injecting memory stress: allocating ${sizeMb}MB during phase: ${phase}`,
			);
			this._injected.push({ type: "memory-stress", phase, sizeMb });
			// Allocate and immediately release
			const buffers = [];
			for (let i = 0; i < sizeMb; i++) {
				buffers.push(Buffer.alloc(1024 * 1024));
			}
			buffers.length = 0;
			if (global.gc) global.gc();
		}
	}

	getInjectedEvents() {
		return [...this._injected];
	}

	clearEvents() {
		this._injected = [];
	}
}
