import { join, resolve } from "path";
import { appendFile, mkdir, stat } from "fs/promises";
import { existsSync } from "fs";

export enum LogLevel {
    DEBUG = 0,
    INFO = 1,
    WARN = 2,
    ERROR = 3,
}

const LogLevelNames = {
    [LogLevel.DEBUG]: "DEBUG",
    [LogLevel.INFO]: "INFO",
    [LogLevel.WARN]: "WARN",
    [LogLevel.ERROR]: "ERROR",
};

export type LogSource = "bot" | "web" | "shared" | "system";

export interface LogEntry {
    timestamp: string;
    level: string;
    source: LogSource;
    message: string;
    data?: any;
    stack?: string;
}

class Logger {
    private logDir: string;
    private errorLogPath: string;
    private initialized: boolean = false;
    private initPromise: Promise<void> | null = null;

    constructor() {
        // Use resolve with __dirname or process.cwd() but make it more robust
        // Since this is in shared/lib/, we can try to find the project root
        // For now, let's stick to a resolved path from process.cwd() or a safer alternative
        this.logDir = resolve(process.cwd(), "logs");
        this.errorLogPath = join(this.logDir, "error.log");
    }

    private async ensureInitialized() {
        if (this.initialized) return;
        if (this.initPromise) return this.initPromise;

        this.initPromise = (async () => {
            try {
                await mkdir(this.logDir, { recursive: true });
                this.initialized = true;
            } catch (err: any) {
                if (err.code === "EEXIST" || err.code === "ENOTDIR") {
                    try {
                        const stats = await stat(this.logDir);
                        if (stats.isDirectory()) {
                            this.initialized = true;
                            return;
                        }
                    } catch (statErr) {}
                }
                console.error(`[SYSTEM] Failed to initialize logger directory at ${this.logDir}:`, err);
            } finally {
                this.initPromise = null;
            }
        })();

        return this.initPromise;
    }

    private safeStringify(data: any): string {
        try {
            return JSON.stringify(data);
        } catch (err) {
            const seen = new WeakSet();
            return JSON.stringify(data, (key, value) => {
                if (typeof value === "object" && value !== null) {
                    if (seen.has(value)) return "[Circular]";
                    seen.add(value);
                }
                return value;
            });
        }
    }

    private formatMessage(entry: LogEntry): string {
        const dataStr = entry.data ? ` | Data: ${this.safeStringify(entry.data)}` : "";
        const stackStr = entry.stack ? `\nStack Trace:\n${entry.stack}` : "";
        return `[${entry.timestamp}] [${entry.level}] [${entry.source.toUpperCase()}] ${entry.message}${dataStr}${stackStr}`;
    }

    private async writeToErrorLog(formatted: string) {
        await this.ensureInitialized();
        try {
            await appendFile(this.errorLogPath, formatted + "\n");
        } catch (err) {
            console.error("[SYSTEM] Failed to write to error log file:", err);
        }
    }

    private log(level: LogLevel, source: LogSource, message: string, errorOrData?: any) {
        const timestamp = new Date().toISOString();
        const levelName = LogLevelNames[level];
        
        const entry: LogEntry = {
            timestamp,
            level: levelName,
            source,
            message,
        };

        if (level === LogLevel.ERROR && errorOrData instanceof Error) {
            entry.stack = errorOrData.stack;
            entry.message = `${message}: ${errorOrData.message}`;
        } else if (errorOrData !== undefined) {
            entry.data = errorOrData;
        }

        const formatted = this.formatMessage(entry);

        // Print to console
        switch (level) {
            case LogLevel.DEBUG:
                console.debug(formatted);
                break;
            case LogLevel.INFO:
                console.log(formatted);
                break;
            case LogLevel.WARN:
                console.warn(formatted);
                break;
            case LogLevel.ERROR:
                console.error(formatted);
                break;
        }

        // Persistent error logging
        if (level === LogLevel.ERROR) {
            this.writeToErrorLog(formatted).catch(() => {
                // Silently fail to avoid infinite loops
            });
        }
    }

    debug(source: LogSource, message: string, data?: any) {
        this.log(LogLevel.DEBUG, source, message, data);
    }

    info(source: LogSource, message: string, data?: any) {
        this.log(LogLevel.INFO, source, message, data);
    }

    warn(source: LogSource, message: string, data?: any) {
        this.log(LogLevel.WARN, source, message, data);
    }

    error(source: LogSource, message: string, error?: any) {
        this.log(LogLevel.ERROR, source, message, error);
    }
}

export const logger = new Logger();
