/**
 * logger.ts — Centralized LogTape configuration for singularity-forge.
 *
 * Purpose: Provide a single, consistent structured logging surface across all
 * SF surfaces (CLI, TUI, web, headless) with automatic PII redaction,
 * per-session file rotation, and mode-aware formatting.
 *
 * Consumer: Every module in src/ and packages/ that needs application logging.
 */

import { mkdirSync } from "node:fs";
import { join } from "node:path";
import { getRotatingFileSink } from "@logtape/file";
import {
	configure,
	getConsoleSink,
	getJsonLinesFormatter,
	type LogRecord,
	getLogger as logtapeGetLogger,
	reset,
	type Sink,
} from "@logtape/logtape";
import { getPrettyFormatter } from "@logtape/pretty";
import { redactByField, redactByPattern } from "@logtape/redaction";

export interface LoggerOptions {
	/** Session identifier for per-session log directories. */
	sessionId?: string;
	/** Runtime mode: dev = pretty console, autonomous = JSON + file. */
	mode?: "dev" | "autonomous";
	/** Override the default log level. */
	level?: "debug" | "info" | "warning" | "error" | "fatal";
	/** Base directory for log files (defaults to cwd/.sf/logs). */
	logDir?: string;
	/** Optional custom sink for testing. */
	customSink?: (record: LogRecord) => void;
	/** Optional category filters for testing. */
	filters?: Array<{
		category: string[];
		lowestLevel: "debug" | "info" | "warning" | "error" | "fatal";
	}>;
}

let configured = false;

export function resetLoggerConfig(): void {
	configured = false;
	try {
		reset();
	} catch {
		/* ignore if not configured */
	}
}

const API_KEY_PATTERN = {
	pattern: /(\b(?:sk|key)-[\w-]+|\bBearer\s+[\w-]+|\bapi_key=[\w-]+)/g,
	replacement: "[REDACTED]",
};

/**
 * Build a redacting sink wrapper that applies pattern-based redaction to
 * formatted output and field-based redaction to structured properties.
 *
 * Purpose: Ensure API keys and home directory paths never leak into logs,
 * regardless of whether they appear in message strings or property values.
 */
function buildRedactingSink(
	downstream: (record: LogRecord) => void,
	homeDir: string,
): Sink {
	// Field-based redaction for structured properties
	const fieldRedacted = redactByField(downstream, {
		fieldPatterns: [/^api[_-]?key$/i, /^token$/i, /^secret$/i, /^password$/i],
		action: () => "[REDACTED]",
	});

	// Pattern-based redaction for message strings (includes home path)
	const homePathPattern = {
		pattern: new RegExp(homeDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"),
		replacement: "~",
	};

	// redactByPattern expects a formatter (returns string), not a sink.
	// For sinks, we apply pattern redaction via a wrapper that mutates
	// the record's message strings before passing to the downstream sink.
	const redactedSink: Sink = (record: LogRecord) => {
		const mutated: LogRecord = {
			...record,
			message: record.message.map((m) => {
				if (typeof m !== "string") return m;
				let s = m;
				s = s.replace(API_KEY_PATTERN.pattern, API_KEY_PATTERN.replacement);
				s = s.replace(homePathPattern.pattern, homePathPattern.replacement);
				return s;
			}) as unknown[],
		};
		fieldRedacted(mutated);
	};

	return redactedSink;
}

/**
 * Configure LogTape sinks and loggers for the current process.
 *
 * Purpose: One-time setup that selects pretty vs JSON output, enables file
 * rotation in autonomous mode, and wraps everything in PII redaction.
 *
 * Consumer: src/cli.ts early in startup, and test suites.
 */
export async function configureLogger(
	options: LoggerOptions = {},
): Promise<void> {
	if (configured) {
		return;
	}

	const mode = options.mode ?? inferMode();
	const level = options.level ?? (mode === "dev" ? "debug" : "info");
	const logDir = options.logDir ?? join(process.cwd(), ".sf", "logs");
	const sessionId = options.sessionId ?? "default";

	const homeDir = process.env.HOME || process.env.USERPROFILE || "/home/user";

	const sinks: Record<string, Sink> = {};

	if (mode === "dev") {
		const prettyFormatter = getPrettyFormatter();
		const redactingFormatter = redactByPattern(prettyFormatter, [
			API_KEY_PATTERN,
			{
				pattern: new RegExp(
					homeDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
					"g",
				),
				replacement: "~",
			},
		]);
		sinks.pretty = getConsoleSink({ formatter: redactingFormatter });
	} else {
		const jsonFormatter = getJsonLinesFormatter();
		const redactingJsonFormatter = redactByPattern(jsonFormatter, [
			API_KEY_PATTERN,
			{
				pattern: new RegExp(
					homeDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
					"g",
				),
				replacement: "~",
			},
		]);
		sinks.console = getConsoleSink({ formatter: redactingJsonFormatter });

		const sessionLogDir = join(logDir, sessionId);
		try {
			mkdirSync(sessionLogDir, { recursive: true });
		} catch {
			/* non-fatal */
		}

		sinks.file = getRotatingFileSink(join(sessionLogDir, "sf.log"), {
			maxSize: 10 * 1024 * 1024,
			maxFiles: 5,
			formatter: redactingJsonFormatter,
		});
	}

	if (options.customSink) {
		sinks.custom = buildRedactingSink(options.customSink, homeDir);
	}

	const loggers = [
		// Silence LogTape's own "loggers are configured" meta info message.
		{
			category: ["logtape", "meta"],
			lowestLevel: "warning" as const,
			sinks: [],
		},
		{
			category: ["sf"],
			lowestLevel: level,
			sinks:
				mode === "dev"
					? ["pretty", "custom"].filter((s) => s in sinks)
					: ["console", "file", "custom"].filter((s) => s in sinks),
		},
	];

	if (options.filters) {
		for (const f of options.filters) {
			loggers.push({
				category: f.category,
				lowestLevel: f.lowestLevel,
				sinks: [],
			});
		}
	}

	await configure({
		sinks,
		loggers,
	});

	configured = true;
}

/**
 * Get a LogTape logger for the given dot-separated category.
 *
 * Purpose: Provide a typed, category-hierarchical logger so modules can
 * declare their logging domain and inherit filters from parent categories.
 *
 * Consumer: Every migrated module calls `const log = getLogger("sf.core.env")`.
 */
export function getLogger(
	category: string,
): ReturnType<typeof logtapeGetLogger> {
	return logtapeGetLogger(category.split("."));
}

function inferMode(): "dev" | "autonomous" {
	if (
		process.env.SF_AUTONOMOUS === "1" ||
		process.env.NODE_ENV === "production"
	) {
		return "autonomous";
	}
	return "dev";
}
