import assert from "node:assert/strict";
import {
	existsSync,
	mkdirSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, test, vi } from "vitest";
import { HaltWatchdog } from "../auto/loop.js";
import { installNotifyInterceptor } from "../bootstrap/notify-interceptor.js";
import {
	_resetNotificationStore,
	initNotificationStore,
} from "../notification-store.js";
import {
	closeDatabase,
	listSelfFeedbackEntries,
	openDatabase,
} from "../sf-db.js";

let testDir;

beforeEach(() => {
	_resetNotificationStore();
	testDir = join(tmpdir(), `sf-halt-self-feedback-test-${Date.now()}`);
	mkdirSync(testDir, { recursive: true });
	writeFileSync(
		join(testDir, "package.json"),
		JSON.stringify({ name: "singularity-forge" }),
	);
	mkdirSync(join(testDir, ".sf"), { recursive: true });
	openDatabase(join(testDir, ".sf", "sf.db"));
	initNotificationStore(testDir);
});

afterEach(() => {
	rmSync(testDir, { recursive: true, force: true });
	_resetNotificationStore();
	closeDatabase();
	vi.useRealTimers();
});

test("HaltWatchdog.check()_when_idle_exceeds_threshold_creates_self_feedback_db_entry_and_markdown", () => {
	vi.useFakeTimers();
	const thresholdMs = 10;
	const watchdog = new HaltWatchdog(testDir, thresholdMs);
	const originalNotify = vi.fn();
	const mockCtx = { ui: { notify: originalNotify }, basePath: testDir };
	installNotifyInterceptor(mockCtx);

	watchdog.heartbeat();
	vi.advanceTimersByTime(thresholdMs + 5);
	const result = watchdog.check(mockCtx, 42);

	assert.equal(result.stuck, true);
	assert.equal(originalNotify.mock.calls.length, 1);

	const entries = listSelfFeedbackEntries();
	assert.equal(entries.length, 1);
	assert.equal(entries[0].kind, "runaway-loop:idle-halt");
	assert.equal(entries[0].severity, "high");
	assert.equal(entries[0].blocking, true);
	assert.match(entries[0].summary, /iteration 42/);
	assert.match(entries[0].summary, /no heartbeat for/);
	assert.match(entries[0].evidence, /elapsed/);
	assert.match(entries[0].evidence, /threshold 10ms/);
	assert.match(entries[0].evidence, /iteration 42/);

	const markdownPath = join(testDir, ".sf", "SELF-FEEDBACK.md");
	assert.equal(existsSync(markdownPath), true);
	const markdown = readFileSync(markdownPath, "utf-8");
	assert.match(markdown, /runaway-loop:idle-halt/);
	assert.match(markdown, /iteration 42/);
});

test("HaltWatchdog.check()_deduplicates_within_same_idle_period", () => {
	vi.useFakeTimers();
	const thresholdMs = 10;
	const watchdog = new HaltWatchdog(testDir, thresholdMs);
	const originalNotify = vi.fn();
	const mockCtx = { ui: { notify: originalNotify }, basePath: testDir };
	installNotifyInterceptor(mockCtx);

	watchdog.heartbeat();
	vi.advanceTimersByTime(thresholdMs + 5);
	watchdog.check(mockCtx, 1);
	watchdog.check(mockCtx, 2);

	assert.equal(listSelfFeedbackEntries().length, 1);
});

test("HaltWatchdog.check()_after_new_heartbeat_creates_second_entry", () => {
	vi.useFakeTimers();
	const thresholdMs = 10;
	const watchdog = new HaltWatchdog(testDir, thresholdMs);
	const originalNotify = vi.fn();
	const mockCtx = { ui: { notify: originalNotify }, basePath: testDir };
	installNotifyInterceptor(mockCtx);

	watchdog.heartbeat();
	vi.advanceTimersByTime(thresholdMs + 5);
	watchdog.check(mockCtx, 1);

	watchdog.heartbeat();
	vi.advanceTimersByTime(thresholdMs + 5);
	watchdog.check(mockCtx, 2);

	const entries = listSelfFeedbackEntries();
	assert.equal(entries.length, 2);
	assert.equal(entries[0].kind, "runaway-loop:idle-halt");
	assert.equal(entries[1].kind, "runaway-loop:idle-halt");

	const markdownPath = join(testDir, ".sf", "SELF-FEEDBACK.md");
	const markdown = readFileSync(markdownPath, "utf-8");
	assert.match(markdown, /runaway-loop:idle-halt/);
});
