/**
 * Dashboard Overlay UOK Diagnostics Tests
 *
 * Purpose: Verify that SFDashboardOverlay consumes writeUokDiagnostics output
 * and renders it consistently with the headless status command.
 *
 * Consumer: TUI users who expect the dashboard to surface the same UOK health
 * information as `sf status` / headless query.
 */

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

// ─── Hoisted mocks ─────────────────────────────────────────────────────────

const mockDiagnostics = vi.hoisted(() => ({
	clear: {
		schemaVersion: 1,
		generatedAt: new Date().toISOString(),
		verdict: "clear",
		classification: "healthy",
		signals: {
			lock: "active",
			parity: "ok",
			ledger: "consistent",
			runtimeProjection: "ok",
			wrapper: "clear",
		},
		currentUnit: null,
		latestRun: null,
		runtimeUnits: [],
		issues: [],
		recommendations: [],
		reportPath: "/tmp/uok-diagnostics.json",
	},
	degraded: {
		schemaVersion: 1,
		generatedAt: new Date().toISOString(),
		verdict: "degraded",
		classification: "needs-repair",
		signals: {
			lock: "stale",
			parity: "ok",
			ledger: "open-runs",
			runtimeProjection: "stale",
			wrapper: "unknown",
		},
		currentUnit: null,
		latestRun: null,
		runtimeUnits: [],
		issues: [
			{
				code: "stale-lock",
				severity: "error",
				message: "Stale auto.lock detected for PID 12345.",
				evidence: { lock: { pid: 12345 } },
			},
			{
				code: "open-ledger-without-live-lock",
				severity: "error",
				message:
					"UOK ledger has 2 started run(s) without a live auto.lock owner.",
				evidence: { runIds: ["run-1", "run-2"] },
			},
		],
		recommendations: [
			"Clear stale auto.lock before dispatch.",
			"Mark orphaned UOK runs recovered or restart from lock owner.",
		],
		reportPath: "/tmp/uok-diagnostics.json",
	},
	attention: {
		schemaVersion: 1,
		generatedAt: new Date().toISOString(),
		verdict: "attention",
		classification: "degraded",
		signals: {
			lock: "active",
			parity: "degraded",
			ledger: "consistent",
			runtimeProjection: "ok",
			wrapper: "unknown",
		},
		currentUnit: { unitType: "execute-task", unitId: "T01", pid: 12345 },
		latestRun: null,
		runtimeUnits: [],
		issues: [
			{
				code: "uok-parity-degraded",
				severity: "warning",
				message:
					"UOK parity degraded: 1 critical mismatch(es), 0 missing exit(s).",
				evidence: { current: { criticalMismatches: 1, missingExitEvents: 0 } },
			},
		],
		recommendations: ["Reconcile UOK parity before mutating git state."],
		reportPath: "/tmp/uok-diagnostics.json",
	},
}));

const dashDataMock = vi.hoisted(() => ({
	basePath: "/tmp/sf-test",
	active: false,
	paused: false,
	remoteSession: null,
	currentUnit: null,
	elapsed: 0,
	rtkEnabled: false,
	rtkSavings: null,
	pendingCaptureCount: 0,
}));

vi.mock("../uok/diagnostic-synthesis.js", () => ({
	writeUokDiagnostics: vi.fn((_basePath, _options) => mockDiagnostics.clear),
}));

vi.mock("../state.js", () => ({
	deriveState: vi.fn(async () => ({
		activeMilestone: null,
		activeSlice: null,
		activeTask: null,
		phase: "idle",
		progress: null,
		nextAction: null,
		blockers: [],
		registry: [],
	})),
}));

vi.mock("../sf-db.js", () => ({
	isDbAvailable: vi.fn(() => false),
	getMilestoneSlices: vi.fn(() => []),
	getSliceTasks: vi.fn(() => []),
}));

vi.mock("../auto.js", () => ({
	getAutoDashboardData: vi.fn(() => dashDataMock),
}));

vi.mock("../auto-dashboard.js", () => ({
	estimateTimeRemaining: vi.fn(() => null),
}));

vi.mock("../progress-score.js", () => ({
	computeProgressScore: vi.fn(() => ({
		level: "green",
		summary: "All systems healthy",
		signals: [],
	})),
}));

vi.mock("../doctor-environment.js", () => ({
	runEnvironmentChecks: vi.fn(() => []),
}));

vi.mock("../worktree-command.js", () => ({
	getActiveWorktreeName: vi.fn(() => null),
}));

vi.mock("../subagent/worker-registry.js", () => ({
	hasActiveWorkers: vi.fn(() => false),
	getWorkerBatches: vi.fn(() => new Map()),
}));

vi.mock("../metrics.js", () => ({
	getLedger: vi.fn(() => null),
	getProjectTotals: vi.fn(() => ({})),
	aggregateByPhase: vi.fn(() => []),
	aggregateBySlice: vi.fn(() => []),
	aggregateByModel: vi.fn(() => []),
	aggregateCacheHitRate: vi.fn(() => 0),
	formatCost: vi.fn((n) => `$${n.toFixed(2)}`),
	formatCostProjection: vi.fn(() => []),
	formatTokenCount: vi.fn((n) => String(n)),
}));

vi.mock("../paths.js", () => ({
	resolveMilestoneFile: vi.fn(() => null),
}));

vi.mock("../files.js", () => ({
	loadFile: vi.fn(async () => null),
}));

vi.mock("../preferences.js", () => ({
	loadEffectiveSFPreferences: vi.fn(() => null),
}));

vi.mock("@singularity-forge/tui", async (importOriginal) => {
	const actual = (await importOriginal()) as any;
	return {
		...actual,
		Key: {
			escape: "\u001B",
			ctrl: (c: string) => `\u0000${c}`,
			ctrlAlt: (c: string) => `\u001B\u0000${c}`,
			ctrlShift: (c: string) => `\u001B\u0000${c.toUpperCase()}`,
			down: "\u001B[B",
			up: "\u001B[A",
		},
		matchesKey: vi.fn(() => false),
		truncateToWidth: vi.fn((s: string, w: number) =>
			s.length > w ? s.slice(0, w) : s,
		),
		visibleWidth: vi.fn((s: string) => s.length),
	};
});

vi.mock("../shared/mod.js", () => ({
	centerLine: vi.fn(
		(s: string, w: number) =>
			" ".repeat(Math.max(0, Math.floor((w - s.length) / 2))) + s,
	),
	fitColumns: vi.fn((parts: string[], _w: number, _sep: string) =>
		parts.join("  "),
	),
	formatDuration: vi.fn((ms: number) => `${Math.round(ms / 1000)}s`),
	joinColumns: vi.fn(
		(left: string, right: string, _w: number) =>
			`${left}${" ".repeat(Math.max(1, _w - left.length - right.length))}${right}`,
	),
	padRight: vi.fn((s: string, w: number) => s.padEnd(w, " ")),
	STATUS_COLOR: {
		done: "success",
		active: "accent",
		pending: "dim",
	},
	STATUS_GLYPH: {
		done: "✓",
		active: "▶",
		pending: "○",
	},
}));

vi.mock("../shortcut-defs.js", () => ({
	formattedShortcutPair: vi.fn(() => "ctrl+alt+g"),
}));

// ─── Helpers ───────────────────────────────────────────────────────────────

function createMockTheme() {
	return {
		fg: vi.fn((color: string, text: string) => `[${color}:${text}]`),
		bold: vi.fn((text: string) => `**${text}**`),
	};
}

function createMockTui() {
	return {
		requestRender: vi.fn(),
	};
}

// ─── Tests ─────────────────────────────────────────────────────────────────

beforeEach(() => {
	vi.clearAllMocks();
});

afterEach(() => {
	vi.clearAllMocks();
});

describe("SFDashboardOverlay UOK diagnostics", () => {
	it("loadData_calls_writeUokDiagnostics_and_stores_result", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		// Prevent interval from firing during test
		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();

		expect(writeUokDiagnostics).toHaveBeenCalledWith("/tmp/sf-test");
		expect(overlay.uokDiagnostics).toEqual(mockDiagnostics.clear);

		overlay.dispose();
	});

	it("loadData_gracefully_handles_writeUokDiagnostics_failure", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		writeUokDiagnostics.mockImplementation(() => {
			throw new Error("disk full");
		});

		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();

		expect(overlay.uokDiagnostics).toBeNull();

		overlay.dispose();
		writeUokDiagnostics.mockRestore();
	});

	it("render_includes_uok_verdict_when_diagnostics_present", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		(writeUokDiagnostics as any).mockReturnValue(mockDiagnostics.degraded);

		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();
		const lines = overlay.buildContentLines(80);
		const text = lines.join("\n");

		expect(text).toContain("UOK");
		expect(text).toContain("degraded");
		expect(text).toContain("needs-repair");

		overlay.dispose();
	});

	it("render_includes_first_issue_code_like_headless_status", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		(writeUokDiagnostics as any).mockReturnValue(mockDiagnostics.degraded);

		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();
		const lines = overlay.buildContentLines(80);
		const text = lines.join("\n");

		// Should contain the first issue code, matching headless status behavior
		expect(text).toContain("stale-lock");

		overlay.dispose();
	});

	it("render_shows_uok_health_section_with_all_issues_when_degraded", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		(writeUokDiagnostics as any).mockReturnValue(mockDiagnostics.degraded);

		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();
		const lines = overlay.buildContentLines(80);
		const text = lines.join("\n");

		// Should show both issue codes in the health section
		expect(text).toContain("stale-lock");
		expect(text).toContain("open-ledger-without-live-lock");

		overlay.dispose();
	});

	it("render_shows_recommendations_when_issues_present", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		(writeUokDiagnostics as any).mockReturnValue(mockDiagnostics.degraded);

		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();
		const lines = overlay.buildContentLines(80);
		const text = lines.join("\n");

		expect(text).toContain("Clear stale auto.lock before dispatch.");
		expect(text).toContain(
			"Mark orphaned UOK runs recovered or restart from lock owner.",
		);

		overlay.dispose();
	});

	it("render_shows_uok_signals_table_when_diagnostics_present", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		(writeUokDiagnostics as any).mockReturnValue(mockDiagnostics.degraded);

		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();
		const lines = overlay.buildContentLines(80);
		const text = lines.join("\n");

		// Signals should be visible
		expect(text).toContain("lock");
		expect(text).toContain("parity");
		expect(text).toContain("ledger");

		overlay.dispose();
	});

	it("render_omits_detailed_uok_section_when_verdict_is_clear", async () => {
		const { writeUokDiagnostics } = await import(
			"../uok/diagnostic-synthesis.js"
		);
		(writeUokDiagnostics as any).mockReturnValue(mockDiagnostics.clear);

		const { SFDashboardOverlay } = await import("../dashboard-overlay.js");

		const tui = createMockTui();
		const theme = createMockTheme();
		const overlay = new SFDashboardOverlay(tui, theme, () => {});

		clearInterval(overlay.refreshTimer);
		overlay.refreshTimer = null as any;

		await overlay.loadData();
		const lines = overlay.buildContentLines(80);
		const text = lines.join("\n");

		// Should show the compact UOK clear line but no issue details
		expect(text).toContain("clear");
		expect(text).not.toContain("stale-lock");

		overlay.dispose();
	});
});
