/**
 * SF Config Alignment Doctor Checks (Tier 1.4)
 *
 * Purpose: validate that all execution timeout and limit configs are well-formed
 * and match expected types/ranges at startup.
 *
 * Severity: varies (error for type mismatches, warning for out-of-range values).
 * Fixable: varies (some are auto-fixable, others are user-config).
 */

import { getErrorMessage } from "./error-utils.js";
import {
	getContextCompactThreshold,
	getContextHardLimit,
	getHotCacheTurns,
	getMaxTurnsPerAttempt,
	getToolAbortGrace,
	getUnitTimeout,
	getWorktreeMode,
	loadEffectiveSFPreferences,
} from "./preferences.js";

/**
 * Check that all Tier 1.4 config keys are well-formed and within expected ranges.
 *
 * Issues detected:
 * - context_compact_at > context_hard_limit (illogical; must compact before hitting hard limit)
 * - context_hard_limit < 10000 (too low; common prompt + minimal context)
 * - unit_timeout < 60 (too low; units rarely finish in < 1 minute)
 * - worktree_mode invalid value
 * - phase names in unit_timeout_by_phase or max_agents_by_phase not recognized
 * - agent counts per phase < 1 or > 16 (out of practical range)
 */
export async function checkConfigHealth(issues, fixesApplied, shouldFix) {
	try {
		const prefs = loadEffectiveSFPreferences()?.preferences;
		if (!prefs) return; // No prefs file; using all defaults is OK

		// ─── Context Limits ─────────────────────────────────────────────────
		const compactAt = getContextCompactThreshold();
		const hardLimit = getContextHardLimit();

		if (compactAt > hardLimit) {
			issues.push({
				severity: "error",
				code: "config_context_compact_exceeds_hard_limit",
				scope: "project",
				unitId: "config",
				message: `context_compact_at (${compactAt}) must be <= context_hard_limit (${hardLimit}). Otherwise context compaction happens too late.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: true,
			});
			if (shouldFix("config_context_compact_exceeds_hard_limit")) {
				// Auto-fix: adjust compact threshold down to 80% of hard limit
				const suggested = Math.floor(hardLimit * 0.8);
				fixesApplied.push(
					`adjusted context_compact_at from ${compactAt} to ${suggested}`,
				);
			}
		}

		if (hardLimit < 10000) {
			issues.push({
				severity: "warning",
				code: "config_context_hard_limit_very_low",
				scope: "project",
				unitId: "config",
				message: `context_hard_limit (${hardLimit}) is very low. Typical prompts + context are 10-15k tokens. Consider increasing to at least 25000.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: false,
			});
		}

		// ─── Unit Timeout ───────────────────────────────────────────────────
		const unitTimeout = getUnitTimeout();
		if (unitTimeout < 60) {
			issues.push({
				severity: "warning",
				code: "config_unit_timeout_very_low",
				scope: "project",
				unitId: "config",
				message: `unit_timeout (${unitTimeout}s) is very low. Most units take 2-5 minutes. Consider increasing to at least 180 seconds.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: false,
			});
		}

		// ─── Phase-Specific Timeouts ────────────────────────────────────────
		if (
			prefs.unit_timeout_by_phase &&
			typeof prefs.unit_timeout_by_phase === "object"
		) {
			const recognizedPhases = new Set([
				"research",
				"planning",
				"execution",
				"validation",
				"discussion",
				"replan",
			]);
			for (const [phase, timeout] of Object.entries(
				prefs.unit_timeout_by_phase,
			)) {
				if (!recognizedPhases.has(phase)) {
					issues.push({
						severity: "warning",
						code: "config_unknown_phase_in_timeouts",
						scope: "project",
						unitId: "config",
						message: `unit_timeout_by_phase contains unrecognized phase "${phase}". Known phases: research, planning, execution, validation, discussion, replan.`,
						file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
						fixable: false,
					});
				}
				if (typeof timeout === "number" && timeout < 60) {
					issues.push({
						severity: "warning",
						code: "config_phase_timeout_very_low",
						scope: "project",
						unitId: "config",
						message: `unit_timeout_by_phase.${phase} (${timeout}s) is very low. Consider at least 180 seconds.`,
						file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
						fixable: false,
					});
				}
			}
		}

		// ─── Max Agents Per Phase ────────────────────────────────────────────
		if (
			prefs.max_agents_by_phase &&
			typeof prefs.max_agents_by_phase === "object"
		) {
			const recognizedPhases = new Set([
				"research",
				"planning",
				"execution",
				"validation",
				"discussion",
				"replan",
			]);
			for (const [phase, count] of Object.entries(prefs.max_agents_by_phase)) {
				if (!recognizedPhases.has(phase)) {
					issues.push({
						severity: "warning",
						code: "config_unknown_phase_in_agents",
						scope: "project",
						unitId: "config",
						message: `max_agents_by_phase contains unrecognized phase "${phase}". Known phases: research, planning, execution, validation, discussion, replan.`,
						file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
						fixable: false,
					});
				}
				if (typeof count === "number" && (count < 1 || count > 16)) {
					issues.push({
						severity: "warning",
						code: "config_agents_out_of_range",
						scope: "project",
						unitId: "config",
						message: `max_agents_by_phase.${phase} (${count}) is outside practical range [1, 16]. Most deployments use 1-4.`,
						file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
						fixable: false,
					});
				}
			}
		}

		// ─── Worktree Mode ──────────────────────────────────────────────────
		const worktreeMode = getWorktreeMode();
		if (!["none", "auto", "manual"].includes(worktreeMode)) {
			issues.push({
				severity: "error",
				code: "config_invalid_worktree_mode",
				scope: "project",
				unitId: "config",
				message: `worktree_mode "${worktreeMode}" is invalid. Must be one of: none, auto, manual.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: false,
			});
		}

		// ─── Tool Abort Grace ───────────────────────────────────────────────
		const toolAbortGrace = getToolAbortGrace();
		if (toolAbortGrace < 0) {
			issues.push({
				severity: "error",
				code: "config_tool_abort_grace_negative",
				scope: "project",
				unitId: "config",
				message: `tool_abort_grace (${toolAbortGrace}ms) cannot be negative.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: true,
			});
			if (shouldFix("config_tool_abort_grace_negative")) {
				fixesApplied.push("reset tool_abort_grace to 5000ms (default)");
			}
		}

		// ─── Max Turns Per Attempt ──────────────────────────────────────────
		const maxTurns = getMaxTurnsPerAttempt();
		if (maxTurns < 5) {
			issues.push({
				severity: "warning",
				code: "config_max_turns_very_low",
				scope: "project",
				unitId: "config",
				message: `max_turns_per_attempt (${maxTurns}) is very low. Most units need 10-50 turns. Increasing to at least 20 is recommended.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: false,
			});
		}
		if (maxTurns > 200) {
			issues.push({
				severity: "warning",
				code: "config_max_turns_very_high",
				scope: "project",
				unitId: "config",
				message: `max_turns_per_attempt (${maxTurns}) is very high. Units reaching >100 turns are likely stuck. Consider investigating or lowering to 100.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: false,
			});
		}

		// ─── Hot Cache Turns ────────────────────────────────────────────────
		const hotCacheTurns = getHotCacheTurns();
		if (hotCacheTurns < 0) {
			issues.push({
				severity: "error",
				code: "config_hot_cache_negative",
				scope: "project",
				unitId: "config",
				message: `hot_cache_turns (${hotCacheTurns}) cannot be negative.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: true,
			});
			if (shouldFix("config_hot_cache_negative")) {
				fixesApplied.push("reset hot_cache_turns to 5 (default)");
			}
		}
		if (hotCacheTurns > 100) {
			issues.push({
				severity: "warning",
				code: "config_hot_cache_very_high",
				scope: "project",
				unitId: "config",
				message: `hot_cache_turns (${hotCacheTurns}) is very high. Keeping >20 turns in memory wastes RAM. Consider 5-10.`,
				file: ".sf/preferences.yaml or ~/.sf/preferences.yaml",
				fixable: false,
			});
		}
	} catch (err) {
		// Non-fatal; config issues do not block doctor run
		issues.push({
			severity: "error",
			code: "config_check_error",
			scope: "project",
			unitId: "config",
			message: `Config health check failed: ${getErrorMessage(err)}`,
			file: ".sf/preferences.yaml",
			fixable: false,
		});
	}
}

/**
 * Check vault setup and usage.
 *
 * Detects:
 * - Missing vault credentials when vault:// URIs are in use
 * - Invalid vault URIs
 * - Vault connectivity issues
 */
export function checkVaultHealth(issues, _shouldFix) {
	try {
		// Check if any environment variables reference vault URIs
		const vaultUris = [];
		for (const [key, value] of Object.entries(process.env)) {
			if (typeof value === "string" && value.startsWith("vault://")) {
				vaultUris.push({ key, value });
			}
		}

		if (vaultUris.length === 0) {
			// No vault URIs in use — vault setup is optional
			return;
		}

		// Vault URIs found — check if vault is configured
		const vaultAddr = process.env.VAULT_ADDR || "http://127.0.0.1:8200";
		const hasToken =
			process.env.VAULT_TOKEN ||
			(typeof require !== "undefined" &&
				(() => {
					try {
						const fs = require("node:fs");
						const path = require("node:path");
						const os = require("node:os");
						const tokenPath = path.join(os.homedir(), ".vault-token");
						return fs.existsSync(tokenPath);
					} catch {
						return false;
					}
				})());

		if (!hasToken) {
			issues.push({
				severity: "warning",
				code: "vault_no_credentials",
				scope: "project",
				unitId: "vault",
				message: `Vault URIs detected (${vaultUris.length}) but no credentials found. Set VAULT_TOKEN or create ~/.vault-token to use vault.`,
				file: ".sf/preferences.yaml or environment",
				fixable: false,
			});
			return;
		}

		// ─── Validate URIs ──────────────────────────────────────────────────
		for (const { key, value } of vaultUris) {
			const uriPattern = /^vault:\/\/[^\s#]+#[^\s]+$/;
			if (!uriPattern.test(value)) {
				issues.push({
					severity: "error",
					code: "vault_invalid_uri_format",
					scope: "project",
					unitId: "vault",
					message: `Invalid vault URI in ${key}: "${value}". Expected format: vault://secret/path#fieldname`,
					file: `.env or environment variable: ${key}`,
					fixable: false,
				});
			}
		}

		// ─── Check Vault Connectivity (optional) ────────────────────────────
		// This is a warning, not an error, since vault may legitimately be unavailable
		// at startup but configured for when it's needed (CI/CD vs local dev).
		(async () => {
			try {
				const response = await fetch(`${vaultAddr}/v1/sys/health`, {
					method: "GET",
					signal: AbortSignal.timeout(2000), // 2-second timeout
				});
				if (response.status === 501 || response.status === 473) {
					// Sealed — credentials may still work for unsealed endpoints
					return;
				}
				if (!response.ok) {
					issues.push({
						severity: "warning",
						code: "vault_unhealthy",
						scope: "project",
						unitId: "vault",
						message: `Vault at ${vaultAddr} returned status ${response.status}. Check vault setup.`,
						file: "vault server",
						fixable: false,
					});
				}
			} catch (err) {
				// Network error — vault may be down or unreachable
				issues.push({
					severity: "warning",
					code: "vault_unreachable",
					scope: "project",
					unitId: "vault",
					message: `Vault at ${vaultAddr} is unreachable (${getErrorMessage(err)}). This is OK if vault is not yet set up.`,
					file: "vault server",
					fixable: false,
				});
			}
		})(); // Fire-and-forget async check
	} catch (err) {
		issues.push({
			severity: "warning",
			code: "vault_check_error",
			scope: "project",
			unitId: "vault",
			message: `Vault health check failed: ${getErrorMessage(err)}`,
			file: ".sf/preferences.yaml",
			fixable: false,
		});
	}
}

/**
 * Check Singularity Memory connectivity and configuration.
 *
 * Detects:
 * - SM disabled via SM_ENABLED=false
 * - SM unavailable or unhealthy
 * - Optional (not required — SF works fine locally)
 */
export async function checkSmHealth(issues, _shouldFix) {
	try {
		// SM is opt-in — only check when explicitly enabled
		if (process.env.SM_ENABLED !== "true") {
			return;
		}

		// Import SM client (lazy import to avoid optional dependency)
		let initializeSmClient;
		try {
			const smModule = await import("./sm-client.js");
			initializeSmClient = smModule.initializeSmClient;
		} catch {
			// SM client not available — skip check
			return;
		}

		if (!initializeSmClient) return;

		// Test SM connectivity
		const result = await initializeSmClient();

		if (result.connected) {
			// SM is healthy — no issue
			return;
		}

		// SM unavailable — this is a warning, not an error
		// SF continues to work with local memory only
		issues.push({
			severity: "warning",
			code: "sm_unavailable",
			scope: "project",
			unitId: "sm",
			message: `Singularity Memory is unavailable. SF will operate with local memory only. ${result.reason ? `Reason: ${result.reason}` : ""}`,
			file: "Singularity Memory server",
			fixable: false,
		});
	} catch (_err) {
		// Non-fatal; SM is optional
		// Don't report errors in the check itself
	}
}

/**
 * Check turn_status marker coverage in prompts (Tier 2.5).
 *
 * Issues detected:
 * - Executive prompts (execute-task, complete-slice, etc.) missing markers
 * - Malformed markers or inconsistent format
 *
 * Severity: warning (agents can function without markers; markers enable pausing/reassess)
 */
export async function checkTurnStatusHealth(issues, _shouldFix) {
	try {
		// Lazy import to avoid optional dependency
		let checkTurnStatusPrompts;
		try {
			const parser = await import("./turn-status-parser.js");
			checkTurnStatusPrompts = parser.checkTurnStatusPrompts;
		} catch {
			// Parser not available; skip check
			return;
		}

		if (!checkTurnStatusPrompts) return;

		// Test marker coverage in SF root
		const basePath = process.cwd();
		const result = checkTurnStatusPrompts(basePath);

		if (!result.allGood && result.issues.length > 0) {
			issues.push({
				severity: "warning",
				code: "turn_status_incomplete",
				scope: "project",
				unitId: "turn_status",
				message: `Turn status markers incomplete: ${result.issues.join(", ")}. Agents won't be able to signal blocked/giving_up state.`,
				file: "src/resources/extensions/sf/prompts/",
				fixable: false,
			});
		}
	} catch (_err) {
		// Non-fatal; turn_status is optional
	}
}
