import { appendFileSync, existsSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import chalk from "chalk";
import {
	type ConsolaOptions,
	type ConsolaReporter,
	createConsola,
	type LogObject,
	type LogType,
} from "consola";

interface LoggerReporterOptions {
	colors: boolean;
}

class LoggerReporter implements ConsolaReporter {
	private colors: boolean;
	constructor({ colors }: Partial<LoggerReporterOptions>) {
		this.colors = colors ?? false;
	}

	private colorFor(type: LogType): (s: string) => string {
		if (!this.colors) return (s) => s;
		switch (type) {
			case "success":
				return chalk.green;
			case "info":
				return chalk.cyan;
			case "warn":
				return chalk.yellow;
			case "error":
			case "fatal":
				return chalk.red;
			case "debug":
				return chalk.magenta;
			case "trace":
				return chalk.gray;
			case "start":
				return chalk.blueBright;
			case "ready":
				return chalk.greenBright;
			case "log":
			case "verbose":
				return chalk.white;
			default:
				return chalk.white;
		}
	}

	private colorMessage(type: LogType, message: string): string {
		if (!this.colors) return message;
		switch (type) {
			case "warn":
				return chalk.yellow(message);
			case "error":
			case "fatal":
				return chalk.red(message);
			default:
				return message;
		}
	}

	log(logObj: LogObject, _ctx: { options: ConsolaOptions }) {
		const date = new Date().toISOString().replace("T", " ").split(".")[0];
		const level = logObj.type.toUpperCase();
		const message = logObj.args.join(" ");
		const className = logObj.tag ?? "-";

		const paint = this.colorFor(logObj.type);
		const levelStr = paint(`[${level}]`);
		const dateStr = this.colors ? chalk.dim(paint(date)) : date;
		const tagStr = this.colors ? chalk.italic(paint(className)) : className;
		const messageStr = this.colorMessage(logObj.type, message);

		console.log(`${dateStr} ${levelStr} ${tagStr}: ${messageStr}`);
	}
}

class FileReporter implements ConsolaReporter {
	private logDir: string;
	private tag: string;

	constructor(tag: string, logDir = "./logs") {
		this.tag = tag;
		this.logDir = logDir;
		this.ensureLogDir();
	}

	private ensureLogDir() {
		if (!existsSync(this.logDir)) {
			mkdirSync(this.logDir, { recursive: true });
		}
	}

	log(logObj: LogObject, _ctx: { options: ConsolaOptions }) {
		const date = new Date().toISOString().replace("T", " ").split(".")[0];
		const level = logObj.type.toUpperCase();
		const message = logObj.args.join(" ");
		const className = logObj.tag ?? this.tag;

		const logLine = `${date} [${level}] ${className}: ${message}\n`;
		const logFile = join(this.logDir, `${this.tag}.log`);

		try {
			appendFileSync(logFile, logLine, "utf8");
		} catch (error) {
			console.error("Failed to write to log file:", error);
		}
	}
}

export function createLogger(tag: string) {
	return createConsola({
		reporters: [
			new LoggerReporter({ colors: process.env.NODE_ENV === "development" }),
			new FileReporter(tag),
		],
		level: process.env.NODE_ENV === "production" ? 3 : 999,
	}).withTag(tag);
}
