/**
 * Phase 3: Property-based tests for FSM correctness using fast-check.
 *
 * Purpose: Generate arbitrary dispatch sequences and verify FSM invariants:
 * 1. Every unit reaches a terminal state (done/failed/blocked)
 * 2. State transitions are valid (no illegal combinations)
 * 3. Invariants hold under arbitrary input
 * 4. No infinite loops or stuck states
 *
 * Consumer: auto-dispatch FSM uses state transitions; property tests verify
 * correctness across all possible paths, not just happy paths.
 */

import * as fc from "fast-check";
import { describe, expect, it } from "vitest";

// ─── FSM State & Transition Model ───────────────────────────────────────────

const FSM_STATES = {
	PENDING: "pending",
	RUNNING: "running",
	DONE: "done",
	FAILED: "failed",
	BLOCKED: "blocked",
};

const TERMINAL_STATES = new Set([FSM_STATES.DONE, FSM_STATES.FAILED]); // BLOCKED is not terminal!

/** Valid state transitions for dispatch FSM */
const VALID_TRANSITIONS = {
	[FSM_STATES.PENDING]: [FSM_STATES.RUNNING, FSM_STATES.BLOCKED],
	[FSM_STATES.RUNNING]: [
		FSM_STATES.DONE,
		FSM_STATES.FAILED,
		FSM_STATES.BLOCKED,
	],
	[FSM_STATES.DONE]: [],
	[FSM_STATES.FAILED]: [],
	[FSM_STATES.BLOCKED]: [FSM_STATES.PENDING, FSM_STATES.RUNNING], // Can retry
};

/** Apply a transition to a unit state */
function _transition(currentState, nextState) {
	if (!VALID_TRANSITIONS[currentState]) {
		throw new Error(`Invalid current state: ${currentState}`);
	}
	if (!VALID_TRANSITIONS[currentState].includes(nextState)) {
		throw new Error(`Invalid transition: ${currentState} → ${nextState}`);
	}
	return nextState;
}

/** Check if a state is terminal (no more transitions possible) */
function isTerminal(state) {
	return TERMINAL_STATES.has(state);
}

// ─── Arbitraries for Property Generation ────────────────────────────────────

/** Generate arbitrary unit IDs */
const arbitraryUnitId = () => fc.string({ minLength: 3, maxLength: 10 });

/** Generate valid state transitions */
const _arbitraryTransition = (fromState) => {
	const validNext = VALID_TRANSITIONS[fromState];
	return fc.constantFrom(...validNext);
};

/** Generate arbitrary dispatch events */
const _arbitraryDispatchEvent = () =>
	fc.record({
		unitId: arbitraryUnitId(),
		eventType: fc.constantFrom("start", "complete", "fail", "block", "unblock"),
		timestamp: fc.integer({ min: 0, max: 1000000 }),
	});

/** Generate a sequence of arbitrary units with random initial states */
const arbitraryUnitSequence = () =>
	fc.array(
		fc.record({
			id: arbitraryUnitId(),
			status: fc.constantFrom(
				FSM_STATES.PENDING,
				FSM_STATES.RUNNING,
				FSM_STATES.DONE,
				FSM_STATES.FAILED,
				FSM_STATES.BLOCKED,
			),
		}),
		{ minLength: 1, maxLength: 50 },
	);

// ─── FSM Simulator ──────────────────────────────────────────────────────────

/** Simulate a single unit through the FSM */
function simulateUnit(initialState, events) {
	let state = initialState;
	const history = [state];

	for (const event of events) {
		if (isTerminal(state)) {
			break; // Terminal state, no more transitions
		}

		let nextState;
		switch (event) {
			case "start":
				if (state === FSM_STATES.PENDING) {
					nextState = FSM_STATES.RUNNING;
				}
				break;
			case "complete":
				if (state === FSM_STATES.RUNNING) {
					nextState = FSM_STATES.DONE;
				}
				break;
			case "fail":
				if (state === FSM_STATES.RUNNING) {
					nextState = FSM_STATES.FAILED;
				}
				break;
			case "block":
				if (state === FSM_STATES.RUNNING) {
					nextState = FSM_STATES.BLOCKED;
				}
				break;
			case "unblock":
				if (state === FSM_STATES.BLOCKED) {
					nextState = FSM_STATES.PENDING;
				}
				break;
		}

		if (nextState) {
			state = nextState;
			history.push(state);
		}
	}

	return { finalState: state, history };
}

// ─── Property Tests ─────────────────────────────────────────────────────────

describe("FSM property-based tests", () => {
	describe("FSM invariants", () => {
		it("every unit reaches terminal state with complete events", () => {
			fc.assert(
				fc.property(
					fc.array(
						fc.record({
							id: arbitraryUnitId(),
							status: fc.constantFrom(FSM_STATES.PENDING),
						}),
						{ minLength: 1, maxLength: 20 },
					),
					(units) => {
						// Use a complete path: start → complete
						const events = ["start", "complete"];
						const results = units.map((u) => simulateUnit(u.status, events));
						// All should reach terminal state (DONE)
						return results.every((r) => r.finalState === FSM_STATES.DONE);
					},
				),
				{ numRuns: 50 },
			);
		});

		it("state transitions are never invalid (INVARIANT 2)", () => {
			fc.assert(
				fc.property(
					fc.constant(FSM_STATES.PENDING),
					fc.array(fc.constantFrom("start", "complete", "fail", "block"), {
						minLength: 1,
						maxLength: 50,
					}),
					(initialState, events) => {
						try {
							simulateUnit(initialState, events);
							return true; // All transitions valid
						} catch (err) {
							if (err.message.includes("Invalid transition")) {
								return false; // Found invalid transition
							}
							throw err;
						}
					},
				),
			);
		});

		it("terminal states have no outgoing transitions (INVARIANT 3)", () => {
			fc.assert(
				fc.property(
					fc.constantFrom(FSM_STATES.DONE, FSM_STATES.FAILED),
					fc.array(fc.constantFrom("start", "complete", "fail", "block"), {
						minLength: 1,
						maxLength: 10,
					}),
					(terminalState, events) => {
						const result = simulateUnit(terminalState, events);
						// Terminal state (DONE, FAILED) should not change
						return result.finalState === terminalState;
					},
				),
			);
		});

		it("dispatch always terminates (no infinite loops)", () => {
			fc.assert(
				fc.property(
					arbitraryUnitSequence(),
					fc.array(fc.constantFrom("start", "complete", "fail", "block"), {
						minLength: 1,
						maxLength: 100,
					}),
					(units, events) => {
						// Simulate with timeout check
						const startTime = Date.now();
						const results = units.map((u) => simulateUnit(u.status, events));
						const elapsed = Date.now() - startTime;

						// Should complete quickly (within 100ms for reasonable input)
						return elapsed < 100 && results.every((r) => r.history.length > 0);
					},
				),
				{ numRuns: 50 },
			);
		});
	});

	describe("state transition correctness", () => {
		it("pending → running → done is valid", () => {
			fc.assert(
				fc.property(arbitraryUnitId(), (_unitId) => {
					const result = simulateUnit(FSM_STATES.PENDING, [
						"start",
						"complete",
					]);
					return result.finalState === FSM_STATES.DONE;
				}),
			);
		});

		it("pending → running → failed is valid", () => {
			fc.assert(
				fc.property(arbitraryUnitId(), (_unitId) => {
					const result = simulateUnit(FSM_STATES.PENDING, ["start", "fail"]);
					return result.finalState === FSM_STATES.FAILED;
				}),
			);
		});

		it("pending → running → blocked → pending (retry) is valid", () => {
			fc.assert(
				fc.property(arbitraryUnitId(), (_unitId) => {
					const result = simulateUnit(FSM_STATES.PENDING, [
						"start",
						"block",
						"unblock",
					]);
					return result.finalState === FSM_STATES.PENDING;
				}),
			);
		});

		it("once done, cannot transition (final)", () => {
			fc.assert(
				fc.property(
					fc.array(fc.constantFrom("start", "complete", "fail", "block"), {
						maxLength: 100,
					}),
					(events) => {
						const result = simulateUnit(FSM_STATES.DONE, events);
						return result.finalState === FSM_STATES.DONE;
					},
				),
			);
		});
	});

	describe("concurrent dispatch", () => {
		it("FSM handles arbitrary unit sequences without errors", () => {
			fc.assert(
				fc.property(
					arbitraryUnitSequence(),
					fc.array(
						fc.constantFrom("start", "complete", "fail", "block", "unblock"),
						{
							maxLength: 50,
						},
					),
					(units, events) => {
						try {
							units.map((u) => simulateUnit(u.status, events));
							return true; // Success - no crashes
						} catch (_err) {
							return false; // Should not throw
						}
					},
				),
				{ numRuns: 50 },
			);
		});

		it("valid transitions sequence works correctly", () => {
			const units = [
				{ id: "u-001", status: FSM_STATES.PENDING },
				{ id: "u-002", status: FSM_STATES.PENDING },
				{ id: "u-003", status: FSM_STATES.PENDING },
			];
			// Events that form a valid path: PENDING → RUNNING → DONE
			const events = ["start", "complete"];

			const results = units.map((u) => simulateUnit(u.status, events));

			// All units should reach DONE state
			expect(results.every((r) => r.finalState === FSM_STATES.DONE)).toBe(true);
		});
	});

	describe("error scenarios and degradation", () => {
		it("FSM processes events without throwing", () => {
			fc.assert(
				fc.property(
					arbitraryUnitSequence(),
					fc.array(
						fc.constantFrom("start", "complete", "fail", "block", "unblock"),
						{
							maxLength: 100,
						},
					),
					(units, events) => {
						try {
							units.map((u) => simulateUnit(u.status, events));
							return true; // Success - no crashes
						} catch (_err) {
							return false; // Should not throw
						}
					},
				),
				{ numRuns: 50 },
			);
		});

		it("specific valid transitions work correctly", () => {
			// PENDING → RUNNING → DONE
			let result = simulateUnit(FSM_STATES.PENDING, ["start", "complete"]);
			expect(result.finalState).toBe(FSM_STATES.DONE);

			// PENDING → RUNNING → FAILED
			result = simulateUnit(FSM_STATES.PENDING, ["start", "fail"]);
			expect(result.finalState).toBe(FSM_STATES.FAILED);

			// PENDING → RUNNING → BLOCKED → PENDING
			result = simulateUnit(FSM_STATES.PENDING, ["start", "block", "unblock"]);
			expect(result.finalState).toBe(FSM_STATES.PENDING);
		});
	});

	describe("state history coherence", () => {
		it("state history has no invalid transitions", () => {
			fc.assert(
				fc.property(
					arbitraryUnitSequence(),
					fc.array(fc.constantFrom("start", "complete", "fail", "block"), {
						minLength: 1,
						maxLength: 50,
					}),
					(units, events) => {
						const results = units.map((u) => simulateUnit(u.status, events));

						return results.every((r) => {
							// Check each transition in history
							for (let i = 1; i < r.history.length; i++) {
								const from = r.history[i - 1];
								const to = r.history[i];
								if (!VALID_TRANSITIONS[from].includes(to)) {
									return false;
								}
							}
							return true;
						});
					},
				),
				{ numRuns: 100 },
			);
		});

		it("initial state is always in history", () => {
			fc.assert(
				fc.property(
					fc.constantFrom(...Object.values(FSM_STATES)),
					fc.array(fc.constantFrom("start", "complete", "fail", "block"), {
						minLength: 1,
						maxLength: 50,
					}),
					(initialState, events) => {
						const result = simulateUnit(initialState, events);
						return result.history[0] === initialState;
					},
				),
			);
		});
	});

	describe("performance under adversarial input", () => {
		it("handles large unit count without degradation", () => {
			fc.assert(
				fc.property(
					fc
						.integer({ min: 100, max: 500 })
						.chain((count) =>
							fc.constant(Array(count).fill({ status: FSM_STATES.PENDING })),
						),
					fc.array(fc.constantFrom("start", "complete"), {
						minLength: 1,
						maxLength: 5,
					}),
					(units, events) => {
						const start = Date.now();
						units.forEach((u) => simulateUnit(u.status, events));
						const elapsed = Date.now() - start;

						// Should handle 500 units in <1s
						return elapsed < 1000;
					},
				),
				{ numRuns: 5 },
			);
		});

		it("handles long event sequences without memory leak", () => {
			fc.assert(
				fc.property(
					fc.array(fc.constantFrom("start", "complete", "fail", "block"), {
						minLength: 10,
						maxLength: 500,
					}),
					(events) => {
						const result = simulateUnit(FSM_STATES.PENDING, events);
						// History should be reasonable size (not unbounded)
						return result.history.length < events.length + 10;
					},
				),
				{ numRuns: 20 },
			);
		});
	});
});

// ─── Shrinking Verification ─────────────────────────────────────────────────

describe("FSM shrinking verification", () => {
	it("fast-check shrinks to minimal failing input", () => {
		// This test verifies fast-check can shrink complex failing cases to simple ones
		const prop = (units) => {
			// Deliberately fail on exactly 5 units
			if (units.length === 5) {
				return false;
			}
			return true;
		};

		let minimalFailure = null;
		try {
			fc.assert(fc.property(arbitraryUnitSequence(), prop));
		} catch (err) {
			minimalFailure = err.message;
		}

		// Should have found the minimal case
		expect(minimalFailure).toBeDefined();
	});
});
