import { homedir } from "node:os";
import { join } from "node:path";
import { z } from "zod";
import { getLogger } from "./logger.js";

const log = getLogger("sf.core.env");

const optionalNonEmptyString = z.string().trim().min(1).optional();

const booleanOneZero = z
	.enum(["0", "1"])
	.optional()
	.transform((value) => value === "1");

// Numeric values
const positiveInteger = z
	.string()
	.transform((v) => parseInt(v, 10))
	.pipe(z.number().int().positive());

const optionalPositiveInteger = positiveInteger.optional();

/**
 * Minimal schema (original, for backward compatibility).
 * These variables are the only ones originally validated.
 */
export const sfEnvSchema = z.object({
	SF_HOME: optionalNonEmptyString,
	SF_AGENT_DIR: optionalNonEmptyString,
	SF_CODING_AGENT_DIR: optionalNonEmptyString,
	SF_STATE_DIR: optionalNonEmptyString,
	SF_PROJECT_ID: z
		.string()
		.trim()
		.regex(/^[A-Za-z0-9_-]+$/, {
			message:
				"SF_PROJECT_ID must contain only letters, numbers, hyphens, and underscores",
		})
		.optional(),
	SF_BIN_PATH: optionalNonEmptyString,
	SF_VERSION: optionalNonEmptyString,
	SF_WEB_PROJECT_CWD: optionalNonEmptyString,
	SF_WEB_DAEMON_MODE: booleanOneZero,
});

/**
 * Comprehensive schema: all SF_* environment variables.
 *
 * Purpose: Provide type-safe, runtime-validated access to all SF_* environment
 * variables with clear error messages for misconfiguration. Prevents silent failures
 * caused by missing or invalid config.
 *
 * Design: Partitions variables into logical groups (core, directories, performance,
 * debug, extensions, recovery, settings). Each variable is documented with its purpose,
 * valid values, and defaults. Optional variables have sensible defaults where applicable.
 *
 * Consumer: loader.ts validates this on startup; modules access config via getCompleteSfEnv().
 */
export const completeSfEnvSchema = sfEnvSchema.extend({
	// Core paths and metadata (set by loader.ts)
	SF_PKG_ROOT: optionalNonEmptyString,
	SF_WORKFLOW_PATH: optionalNonEmptyString,
	SF_BUNDLED_EXTENSION_PATHS: optionalNonEmptyString,

	// Directories with defaults
	SF_WORKSPACE_BASE: optionalNonEmptyString,
	SF_HISTORY_BASE: optionalNonEmptyString,
	SF_NOTIFICATIONS_BASE: optionalNonEmptyString,
	SF_SCHEDULE_FILE: optionalNonEmptyString,
	SF_RECOVERY_BASE: optionalNonEmptyString,
	SF_FORENSICS_BASE: optionalNonEmptyString,
	SF_CLEANUP_BASE: optionalNonEmptyString,
	SF_EXPORT_BASE: optionalNonEmptyString,
	SF_CAPTURES_BASE: optionalNonEmptyString,
	SF_UNDO_BASE: optionalNonEmptyString,
	SF_SKILL_HEALTH_BASE: optionalNonEmptyString,
	SF_DOCTOR_BASE: optionalNonEmptyString,
	SF_SETTINGS_BASE: optionalNonEmptyString,
	SF_PROJECT_ROOT: optionalNonEmptyString,
	SF_NODE_BIN: optionalNonEmptyString,

	// Performance tuning
	SF_RTK_DISABLED: booleanOneZero,
	SF_RTK_PATH: optionalNonEmptyString,
	SF_RTK_REWRITE_TIMEOUT_MS: optionalPositiveInteger,
	SF_CIRCUIT_BREAKER_OPEN_DURATION_MS: optionalPositiveInteger,
	SF_CIRCUIT_BREAKER_FAILURE_THRESHOLD: optionalPositiveInteger,
	SF_CIRCUIT_BREAKER_HALF_OPEN_MAX_ATTEMPTS: optionalPositiveInteger,
	SF_HEADLESS_PROMPT_TRACE_CHARS: optionalPositiveInteger,

	// Debug flags
	SF_QUIET: booleanOneZero,
	SF_DEBUG: booleanOneZero,
	SF_DEBUG_EXTENSIONS: booleanOneZero,
	SF_TRACE_ENABLED: booleanOneZero,
	SF_HEADLESS: booleanOneZero,
	SF_HEADLESS_PROMPT_TRACE: booleanOneZero,
	SF_STARTUP_TIMING: booleanOneZero,
	SF_SHOW_TOKEN_COST: booleanOneZero,
	SF_FIRST_RUN_BANNER: booleanOneZero,
	SF_DISABLE_STARTUP_DOCTOR: booleanOneZero,
	SF_ENGINE_BYPASS: booleanOneZero,
	SF_DISABLE_NATIVE_SF_PARSER: booleanOneZero,
	SF_DISABLE_NATIVE_SF_GIT: booleanOneZero,

	// Extensions
	SF_SKILL_MANIFEST_STRICT: booleanOneZero,
	SF_PERMISSION_LEVEL: z
		.enum(["minimal", "low", "medium", "high", "bypassed"])
		.optional(),
	SF_GEMINI_PERMISSION_MODE: z.enum(["ask", "auto", "deny"]).optional(),
	SF_SESSION_BROWSER_DIR: optionalNonEmptyString,
	SF_SESSION_BROWSER_CWD: optionalNonEmptyString,
	SF_FETCH_ALLOWED_URLS: optionalNonEmptyString,
	SF_ALLOWED_COMMAND_PREFIXES: optionalNonEmptyString,

	// Recovery and dispatch
	SF_RECOVERY_DOCTOR_MODULE: optionalNonEmptyString,
	SF_RECOVERY_FORENSICS_MODULE: optionalNonEmptyString,
	SF_RECOVERY_SCOPE: z.enum(["unit", "milestone", "global"]).optional(),
	SF_RECOVERY_SESSION_FILE: optionalNonEmptyString,
	SF_RECOVERY_ACTIVITY_DIR: optionalNonEmptyString,
	SF_PARALLEL_WORKER: booleanOneZero,
	SF_WORKER_MODEL: optionalNonEmptyString,
	SF_MILESTONE_LOCK: optionalNonEmptyString,
	SF_SLICE_LOCK: optionalNonEmptyString,
	SF_WORKTREE: optionalNonEmptyString,
	SF_CLI_WORKTREE: optionalNonEmptyString,
	SF_CLI_WORKTREE_BASE: optionalNonEmptyString,
	SF_CLEANUP_BRANCHES: booleanOneZero,
	SF_CLEANUP_SNAPSHOTS: booleanOneZero,
	SF_AUTO_WORKTREE: optionalNonEmptyString,

	// Settings modules
	SF_SETTINGS_BUDGET_MODULE: optionalNonEmptyString,
	SF_SETTINGS_HISTORY_MODULE: optionalNonEmptyString,
	SF_SETTINGS_METRICS_MODULE: optionalNonEmptyString,
	SF_SETTINGS_PREFS_MODULE: optionalNonEmptyString,
	SF_SETTINGS_ROUTER_MODULE: optionalNonEmptyString,
	SF_WORKSPACE_MODULE: optionalNonEmptyString,
	SF_SESSION_MANAGER_MODULE: optionalNonEmptyString,

	// Miscellaneous
	SF_TRIAGE_SUFFIX: optionalNonEmptyString,
	SF_DOCTOR_SCOPE: z.enum(["fast", "normal", "deep"]).optional(),
	SF_EXPORT_FORMAT: z.enum(["json", "csv", "markdown"]).optional(),
	SF_TARGET_SESSION_NAME: optionalNonEmptyString,
	SF_TARGET_SESSION_PATH: optionalNonEmptyString,
	SF_VISUALIZER_BASE: optionalNonEmptyString,
	SF_RECOVERY_UNIT_ID: optionalNonEmptyString,
	SF_RECOVERY_UNIT_TYPE: optionalNonEmptyString,
});

export type SfEnv = z.infer<typeof sfEnvSchema>;

export type CompleteSfEnv = z.infer<typeof completeSfEnvSchema>;

/**
 * Parse supported SF_* environment variables into a typed object.
 *
 * Purpose: give runtime code a shared contract for SF-specific environment
 * variables instead of scattering ad hoc `process.env` parsing across entry
 * points.
 *
 * Consumer: root CLI/headless modules and web bridge code that need stable SF
 * path and mode values.
 */
export function parseSfEnv(env: NodeJS.ProcessEnv = process.env): SfEnv {
	return sfEnvSchema.parse(env);
}

/**
 * Parse all known SF_* environment variables (comprehensive validation).
 *
 * Purpose: Validate complete environment configuration at startup, catching
 * misconfiguration early with clear error messages.
 *
 * Consumer: loader.ts calls this during initialization.
 *
 * Error handling: If validation fails, returns safe defaults rather than
 * throwing (graceful degradation). Critical errors logged to stderr.
 */
export function parseCompleteSfEnv(
	env: NodeJS.ProcessEnv = process.env,
): CompleteSfEnv {
	const result = completeSfEnvSchema.safeParse(env);

	if (!result.success) {
		// Log validation errors for debugging (but don't crash)
		const errors = result.error.issues.slice(0, 5); // First 5 errors
		const message = errors
			.map((e: z.ZodIssue) => `  ${e.path.join(".")}: ${e.message}`)
			.join("\n");

		if (process.env.SF_DEBUG) {
			log.warn(
				`Environment validation issues (first 5):\n${message}\n` +
					`Set SF_QUIET=1 to suppress this check.\n`,
			);
		}
	}

	return result.data || ({} as CompleteSfEnv);
}

/**
 * Return typed SF environment values with path defaults applied.
 *
 * Purpose: centralize default path behavior for SF_HOME and the managed agent
 * directory while still validating user-provided overrides.
 *
 * Consumer: app-paths.ts, cli-logs.ts, headless-query.ts, and future env readers.
 */
export function getSfEnv(env: NodeJS.ProcessEnv = process.env) {
	const parsed = parseSfEnv(env);
	const sfHome = parsed.SF_HOME ?? join(homedir(), ".sf");
	const agentDir =
		parsed.SF_AGENT_DIR ?? parsed.SF_CODING_AGENT_DIR ?? join(sfHome, "agent");
	return {
		...parsed,
		sfHome,
		agentDir,
	};
}

/**
 * Get all known SF_* environment values with defaults applied.
 *
 * Purpose: Single source of truth for complete environment configuration
 * throughout the application. All modules needing environment access should
 * use this function instead of direct process.env reads.
 *
 * Consumer: Extensions, dispatch loop, recovery modules.
 *
 * Design: Applies sensible defaults for optional variables. For example,
 * STATE_DIR defaults to SF_HOME if not set. Relative paths are NOT converted
 * to absolute (caller responsibility).
 */
export function getCompleteSfEnv(
	env: NodeJS.ProcessEnv = process.env,
): CompleteSfEnv & {
	sfHome: string;
	agentDir: string;
	stateDir: string;
} {
	const parsed = parseCompleteSfEnv(env);
	const sfHome = parsed.SF_HOME ?? join(homedir(), ".sf");
	const agentDir =
		parsed.SF_AGENT_DIR ?? parsed.SF_CODING_AGENT_DIR ?? join(sfHome, "agent");
	const stateDir = parsed.SF_STATE_DIR ?? sfHome;

	return {
		...parsed,
		sfHome,
		agentDir,
		stateDir,
	};
}

/**
 * Get validation summary: which SF_* variables are set vs defaults applied.
 *
 * Purpose: For debugging and diagnostics. Shows which variables were explicitly
 * set vs using built-in defaults.
 *
 * Consumer: startup doctor, debug utilities.
 */
export function getEnvValidationSummary(env: NodeJS.ProcessEnv = process.env): {
	configured: string[];
	defaults: string[];
	total: number;
} {
	const configured: string[] = [];
	const defaults: string[] = [];

	// List of all known SF_* variables
	const knownVars = Object.keys(completeSfEnvSchema.shape);

	for (const varName of knownVars) {
		if (env[varName] !== undefined) {
			configured.push(varName);
		} else {
			defaults.push(varName);
		}
	}

	return {
		configured,
		defaults,
		total: knownVars.length,
	};
}
