import { LOG } from '#constants/index';
import { existsSync, mkdirSync } from 'fs';
import { appendFile } from 'fs/promises';
import { resolve } from 'path';
import { maskSensitiveFields } from '#utils/logUtils';
import Config from '#config';

declare global {
	// eslint-disable-next-line no-var
	var __baseDir: string;
}

/**
 * Optional context fields to attach to each log entry.
 */
export interface LogContext {
	userId?: string | null;
	requestId?: string | null;
	ipAddress?: string | null;
	tenantId?: string | null;
}

class LoggingService {
	logChannelType: string;
	logType: string;
	isLogEnabled: boolean;
	logUrl: string;
	logMode: string;
	timeOut: number;

	constructor() {
		this.logChannelType = LOG.LOG_CHANNEL_TYPE;
		this.logType = LOG.LOG_TYPE;
		this.isLogEnabled = (Config.logging.level ?? 'info') !== 'off';
		this.logUrl = Config.logging.url ?? './storage/logs';
		this.logMode = Config.logging.mode ?? 'file';
		this.timeOut = Config.logging.timeout ?? 5000;
	}

	async logAsync(category: string, source: string, message: string | Error, params: unknown, ctx?: LogContext) {
		try {
			if (!this.isLogEnabled) return;

			let messageStr: string;
			if (message instanceof Error) {
				const errObj: Record<string, string> = { message: message.message };
				if (message.stack) errObj.stack = message.stack;
				messageStr = JSON.stringify(errObj);
			} else {
				messageStr = String(message);
			}

			// Flat log entry (structured JSON — machine parseable)
			const logEntry: Record<string, unknown> = {
				service: this.logChannelType,
				env: Config.server.projectName, // Use project name instead of NODE_ENV
				level: category,
				source,
				message: messageStr.replace(/(\\n)|\\|"|\s{2,}/g, ' ').trim(),
				params: params !== null && params !== undefined ? maskSensitiveFields(params) : undefined,
				userId: ctx?.userId ?? null,
				requestId: ctx?.requestId ?? null,
				ipAddress: ctx?.ipAddress ?? null,
				tenantId: ctx?.tenantId ?? null,
				createdAt: new Date().toISOString(),
			};

			if (['console', 'both'].includes(this.logMode)) {
				console.log(JSON.stringify(logEntry));
				process.stdout.write('');
			}
			if (this.logMode !== 'console') {
				const date = new Date();
				const dayLog =
					date.getDate().toString().padStart(2, '0') +
					(date.getMonth() + 1).toString().padStart(2, '0') +
					date.getFullYear();
				const timeRange =
					date.getHours().toString().padStart(2, '0') +
					'00_' +
					(date.getHours() + 1).toString().padStart(2, '0') +
					'00';

				const store = resolve(
					(global.__baseDir || process.cwd()).replace('/dist', ''),
					'storage/logs',
					category === LOG.LOG_DB_CATEGORY ? 'database' : '',
				);
				const folderPath = resolve(store, dayLog);
				const filePath = resolve(folderPath, `${timeRange}.log`);
				const fileContent = `${JSON.stringify(logEntry)}\n`;

				if (!existsSync(folderPath)) mkdirSync(folderPath, { recursive: true });
				await appendFile(filePath, fileContent, { flag: 'a+' });
			}
		} catch (ex) {
			console.log(ex);
			process.stdout.write('');
		}
	}

	async logErrorAsync(source: string, message: Error, params: unknown, ctx?: LogContext) {
		await this.logAsync(LOG.LOG_ERROR_CATEGORY, source, message, params, ctx);
	}

	async logInfoAsync(source: string, message: Error | string, params: unknown, ctx?: LogContext) {
		await this.logAsync(
			LOG.LOG_INFO_CATEGORY,
			source,
			message instanceof Error ? message : new Error(String(message)),
			params,
			ctx,
		);
	}

	async logTransAsync(source: string, message: Error | string, params: unknown, ctx?: LogContext) {
		await this.logAsync(
			LOG.LOG_TRANS_CATEGORY,
			source,
			message instanceof Error ? message : new Error(String(message)),
			params,
			ctx,
		);
	}

	async logDBAsync(query: unknown) {
		await this.logAsync(LOG.LOG_DB_CATEGORY, 'Sequelize', String(query), null);
	}
}

export default LoggingService;
export { LoggingService };
