import 'dotenv/config';
import { ConfigSchema, type Config } from './schema';
import { EnvSchema, type EnvVars } from './env-schema';
import { baseConfig } from './base';
import { developmentConfig } from './development';
import { productionConfig } from './production';
import { stagingConfig } from './staging';

// Deep merge function to combine config objects
function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
	const output = { ...target };
	for (const key in source) {
		if (
			source[key] !== null &&
			typeof source[key] === 'object' &&
			!Array.isArray(source[key]) &&
			key in target &&
			target[key] !== null &&
			typeof target[key] === 'object' &&
			!Array.isArray(target[key])
		) {
			output[key] = deepMerge(target[key] as any, source[key] as any);
		} else {
			output[key] = source[key] as any;
		}
	}
	return output;
}

// Recursive deep freeze to prevent runtime modifications
function deepFreeze<T>(obj: T): T {
	if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
		Object.freeze(obj);
		Object.getOwnPropertyNames(obj).forEach((prop) => {
			deepFreeze((obj as any)[prop]);
		});
	}
	return obj;
}

// Get environment-specific config
function getEnvConfig(): Partial<Config> {
	const nodeEnv = process.env.NODE_ENV || 'development';

	switch (nodeEnv) {
		case 'production':
			return productionConfig;
		case 'staging':
			return stagingConfig;
		case 'test':
			// Use development config for tests
			return developmentConfig;
		case 'development':
		default:
			return developmentConfig;
	}
}

// Build config by merging base + env-specific + environment variables (schema-driven)
function buildConfig(): Config {
	const nodeEnv = process.env.NODE_ENV || 'development';
	const envConfig = getEnvConfig();
	const merged = deepMerge(baseConfig, envConfig);

	// Log config loading
	console.log(`[Config] NODE_ENV: ${nodeEnv}`);
	console.log(
		`[Config] Using config: ${envConfig === productionConfig ? 'production' : envConfig === stagingConfig ? 'staging' : envConfig === developmentConfig ? 'development' : 'unknown'}`,
	);

	// Parse environment variables with schema (type-safe, auto-coercion, validation)
	const envVars: EnvVars = EnvSchema.parse(process.env);

	// Apply parsed environment variables to config
	const config = deepMerge(merged, {
		server: {
			port: envVars.PORT,
			backendUrl: envVars.BACKEND_URL,
			frontendUrl: envVars.FRONTEND_URL,
			projectName: envVars.PROJECT_NAME,
			projectVersion: envVars.PROJECT_VERSION,
		},
		database: {
			host: envVars.DB_HOST,
			port: envVars.DB_PORT,
			user: envVars.DB_USER,
			password: envVars.DB_PASSWORD,
			name: envVars.DB_NAME,
			slowQueryThresholdMs: envVars.SLOW_QUERY_THRESHOLD_MS,
			pool: {
				max: envVars.DB_POOL_MAX,
				min: envVars.DB_POOL_MIN,
				acquire: envVars.DB_POOL_ACQUIRE,
				idle: envVars.DB_POOL_IDLE,
				evict: envVars.DB_POOL_EVICT,
			},
		},
		redis: {
			enabled: envVars.REDIS_ENABLED,
			host: envVars.REDIS_HOST,
			port: envVars.REDIS_PORT,
			password: envVars.REDIS_PASSWORD,
			db: envVars.REDIS_DB,
			connectTimeout: envVars.REDIS_CONNECT_TIMEOUT,
			commandTimeout: envVars.REDIS_COMMAND_TIMEOUT,
			maxRetries: envVars.REDIS_MAX_RETRIES,
			ipFamily: envVars.REDIS_IP_FAMILY,
		},
		redisA: {
			enabled: envVars.REDIS_A_ENABLED,
			host: envVars.REDIS_A_HOST,
			port: envVars.REDIS_A_PORT,
			password: envVars.REDIS_A_PASSWORD,
			cluster: envVars.REDIS_A_CLUSTER,
			clusterNodes: envVars.REDIS_A_CLUSTER_NODES,
		},
		redisB: {
			host: envVars.REDIS_B_HOST,
			port: envVars.REDIS_B_PORT,
			password: envVars.REDIS_B_PASSWORD,
			db: envVars.REDIS_B_DB,
		},
		auth: {
			jwtSecret: envVars.JWT_SECRET,
			jwtSecretPrevious: envVars.JWT_SECRET_PREVIOUS ? envVars.JWT_SECRET_PREVIOUS.split(',').filter(Boolean) : [],
			jwtRefreshSecret: envVars.JWT_REFRESH_SECRET,
			jwtRefreshSecretPrevious: envVars.JWT_REFRESH_SECRET_PREVIOUS
				? envVars.JWT_REFRESH_SECRET_PREVIOUS.split(',').filter(Boolean)
				: [],
			jwtExpiresIn: envVars.JWT_EXPIRES_IN,
			jwtRefreshExpiresIn: envVars.JWT_REFRESH_EXPIRES_IN,
			tokenEncryptionKey: envVars.TOKEN_ENCRYPTION_KEY,
			tokenEncryptionKeyPrevious: envVars.TOKEN_ENCRYPTION_KEY_PREVIOUS
				? envVars.TOKEN_ENCRYPTION_KEY_PREVIOUS.split(',').filter(Boolean)
				: [],
			bcryptRounds: envVars.BCRYPT_ROUNDS,
			defaultPassword: envVars.DEFAULT_PASSWORD,
			passwordMinLength: envVars.PASSWORD_MIN_LENGTH,
			passwordMaxLength: envVars.PASSWORD_MAX_LENGTH,
			includeTokensInResponse: envVars.INCLUDE_TOKENS_IN_RESPONSE,
			skipSensitiveConfirm: envVars.SKIP_SENSITIVE_CONFIRM,
			enableRegister: envVars.ENABLE_REGISTER,
			passwordHistoryLimit: envVars.PASSWORD_HISTORY_LIMIT,
		},
		cookie: {
			// Only override domain if env var is explicitly set (not default)
			domain: process.env.COOKIE_DOMAIN ? envVars.COOKIE_DOMAIN : merged.cookie.domain,
			crossSite: envVars.COOKIE_CROSS_SITE,
			forceSecure: envVars.FORCE_SECURE_COOKIES,
			devSameSiteLax: envVars.DEV_SAMESITE_LAX,
			// Preserve basePath from env-specific config (e.g., /bekind for staging)
			basePath: process.env.COOKIE_BASE_PATH ?? merged.cookie.basePath ?? '',
		},
		csrf: {
			originValidation: envVars.CSRF_ORIGIN_VALIDATION,
			doubleSubmit: envVars.CSRF_DOUBLE_SUBMIT,
			httpOnly: envVars.CSRF_HTTPONLY,
			devSameSiteLax: envVars.DEV_SAMESITE_LAX,
			allowedOrigins: envVars.ALLOWED_ORIGINS ? envVars.ALLOWED_ORIGINS.split(',') : [],
		},
		uploads: {
			path: envVars.UPLOAD_PATH,
			maxFileSize: envVars.MAX_FILE_SIZE,
			imagesPath: '/storage/uploads/images',
			videosPath: '/storage/uploads/videos',
			filesPath: '/storage/uploads/files',
			maxSize: envVars.MAX_FILE_SIZE,
			storageImagesPath: envVars.STORAGE_IMAGES_PATH,
			compressTimeoutMs: envVars.COMPRESS_TIMEOUT_MS,
		},
		storage: {
			provider: envVars.STORAGE_PROVIDER,
			publicBaseUrl: envVars.PUBLIC_BASE_URL,
			localStorageRoot: envVars.LOCAL_STORAGE_ROOT,
			minio: {
				endpoint: envVars.MINIO_ENDPOINT,
				port: envVars.MINIO_PORT,
				useSSL: envVars.MINIO_USE_SSL,
				accessKey: envVars.MINIO_ACCESS_KEY,
				secretKey: envVars.MINIO_SECRET_KEY,
				bucket: envVars.MINIO_BUCKET,
				region: envVars.MINIO_REGION,
				s3Bucket: envVars.S3_BUCKET,
			},
			quotaPerUser: envVars.STORAGE_QUOTA_PER_USER,
		},
		email: {
			host: envVars.EMAIL_HOST,
			port: envVars.EMAIL_PORT,
			user: envVars.EMAIL_USER,
			pass: envVars.EMAIL_PASS,
			from: envVars.EMAIL_FROM,
		},
		notifications: {
			queue: {
				concurrency: envVars.QUEUE_CONCURRENCY,
				workerConcurrency: envVars.WORKER_CONCURRENCY,
				attempts: envVars.JOB_ATTEMPTS,
				backoffDelay: envVars.JOB_BACKOFF_DELAY,
			},
			rateLimiting: {
				maxLoginAttempts: envVars.MAX_LOGIN_ATTEMPTS,
				lockoutDurationMinutes: envVars.LOCKOUT_DURATION_MINUTES,
				rateLimitWindowMs: envVars.RATE_LIMIT_WINDOW_MS,
				rateLimitOnesignalMax: envVars.RATE_LIMIT_ONESIGNAL_MAX,
				rateLimitZaloMax: envVars.RATE_LIMIT_ZALO_MAX,
				rateLimitEmailMax: envVars.RATE_LIMIT_EMAIL_MAX,
			},
			cacheTtl: {
				perm: envVars.PERM_CACHE_TTL,
				session: envVars.SESSION_CACHE_TTL,
				reuseWindowSeconds: envVars.REUSE_WINDOW_SECONDS,
			},
			maxRetries: envVars.MAX_NOTIFICATION_RETRIES,
		},
		zalo: {
			appId: envVars.ZALO_APP_ID,
			secretKey: envVars.ZALO_SECRET_KEY,
			oaAccessToken: envVars.ZALO_OA_ACCESS_TOKEN,
			znsAccessToken: envVars.ZALO_ZNS_ACCESS_TOKEN,
			znsTemplateId: envVars.ZALO_ZNS_TEMPLATE_ID,
			serviceJwtSecret: envVars.SERVICE_JWT_SECRET || envVars.JWT_SECRET,
			mtlsEnabled: envVars.MTLS_ENABLED,
			workloadIdentityEnabled: envVars.WORKLOAD_IDENTITY_ENABLED,
		},
		oneSignal: {
			appId: envVars.ONESIGNAL_APP_ID,
			apiKey: envVars.ONESIGNAL_API_KEY,
		},
		vapid: {
			subject: envVars.VAPID_SUBJECT,
			publicKey: envVars.VAPID_PUBLIC_KEY,
			privateKey: envVars.VAPID_PRIVATE_KEY,
		},
		idempotency: {
			keyTtl: envVars.IDEMPOTENCY_KEY_TTL,
		},
		circuitBreaker: {
			errorRate: envVars.CIRCUIT_BREAKER_ERROR_RATE,
			minRequests: envVars.CIRCUIT_BREAKER_MIN_REQUESTS,
			timeout: envVars.CIRCUIT_BREAKER_TIMEOUT,
		},
		partition: {
			retainMonths: envVars.PARTITION_RETAIN_MONTHS,
		},
		virusScan: {
			enabled: envVars.VIRUS_SCAN_ENABLED,
			clamSocket: envVars.CLAM_SOCKET,
			clamHost: envVars.CLAM_HOST,
			clamPort: envVars.CLAM_PORT,
			clamScanTimeoutMs: envVars.CLAM_SCAN_TIMEOUT_MS,
			failOpen: envVars.VIRUS_SCAN_FAIL_OPEN,
		},
		logging: {
			level: envVars.LOG_LEVEL,
			url: envVars.LOG_URL,
			mode: envVars.LOG_MODE,
			timeout: envVars.LOG_TIMEOUT,
		},
		cors: {
			devDisable: envVars.DEV_CORS_DISABLE,
		},
		featureFlags: {
			enableRedis: envVars.REDIS_ENABLED,
			enableVirusScan: envVars.VIRUS_SCAN_ENABLED,
			enableCSRF: envVars.CSRF_ORIGIN_VALIDATION || envVars.CSRF_DOUBLE_SUBMIT,
			enableRateLimit: true,
			showStackInErrors: envVars.SHOW_STACK_IN_ERRORS,
			fileListUrlConcurrency: envVars.FILE_LIST_URL_CONCURRENCY,
			maxSimpleFallbackSize: envVars.MAX_SIMPLE_FALLBACK_SIZE,
			maxFallbackMapSize: envVars.MAX_FALLBACK_MAP_SIZE,
		},
		audit: {
			mode: envVars.AUDIT_MODE || 'direct',
			enabled: envVars.AUDIT_ENABLED,
		},
		runtime: {
			logLevel: envVars.LOG_LEVEL,
			batchSize: 100,
			pollInterval: 500,
		},
		webhook: {
			token: envVars.WEBHOOK_TOKEN,
		},
	});

	return config as Config;
}

// Log configuration sources for debugging
function logConfigSources(config: Config, envConfig: Partial<Config>) {
	if (process.env.LOG_CONFIG_SOURCES !== 'true') return;

	console.log('=== Configuration Sources ===');
	console.log(`NODE_ENV=${process.env.NODE_ENV || 'development'}`);

	const getSource = (value: any, baseValue: any, envValue: any, envVar: string): string => {
		if (envVar && process.env[envVar]) return `process.env.${envVar}`;
		if (envValue !== undefined && envValue !== baseValue) return 'env-config';
		return 'base';
	};

	console.log(
		`PORT=${config.server.port} (from ${getSource(config.server.port, baseConfig.server.port, envConfig.server?.port, 'PORT')})`,
	);
	console.log(
		`BACKEND_URL=${config.server.backendUrl} (from ${getSource(config.server.backendUrl, baseConfig.server.backendUrl, envConfig.server?.backendUrl, 'BACKEND_URL')})`,
	);
	console.log(
		`REDIS_ENABLED=${config.redis.enabled} (from ${getSource(config.redis.enabled, baseConfig.redis.enabled, envConfig.redis?.enabled, 'REDIS_ENABLED')})`,
	);
	console.log(
		`DB_HOST=${config.database.host} (from ${getSource(config.database.host, baseConfig.database.host, envConfig.database?.host, 'DB_HOST')})`,
	);
	console.log(
		`STORAGE_PROVIDER=${config.storage.provider} (from ${getSource(config.storage.provider, baseConfig.storage.provider, envConfig.storage?.provider, 'STORAGE_PROVIDER')})`,
	);
	console.log('============================');
}

// Validate and freeze config
const rawConfig = buildConfig();

// Validate with zod schema - this will throw if config is invalid
const validatedConfig = ConfigSchema.parse(rawConfig);

// Deep freeze the config object to prevent runtime modifications (including nested objects)
const appConfig = deepFreeze(validatedConfig);

// Log configuration sources for debugging (if enabled)
logConfigSources(appConfig, getEnvConfig());

export { appConfig as Config };
export type { Config as ConfigType };
export default appConfig;
