import { expect, test, describe, beforeAll, afterAll, spyOn } from "bun:test";
import { logger } from "./logger";
import { existsSync, unlinkSync, readFileSync, writeFileSync } from "fs";
import { join } from "path";

describe("Logger", () => {
    const logDir = join(process.cwd(), "logs");
    const logFile = join(logDir, "error.log");

    beforeAll(() => {
        // Cleanup if exists
        try {
            if (existsSync(logFile)) unlinkSync(logFile);
        } catch (e) {}
    });

    test("should log info messages to console with correct format", () => {
        const spy = spyOn(console, "log");
        const message = "Formatting test";
        logger.info("system", message);
        
        expect(spy).toHaveBeenCalled();
        const callArgs = spy.mock.calls[0]?.[0];
        expect(callArgs).toBeDefined();
        if (callArgs) {
            // Strict regex check for ISO timestamp and format
            const regex = /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[INFO\] \[SYSTEM\] Formatting test$/;
            expect(callArgs).toMatch(regex);
        }
        spy.mockRestore();
    });

    test("should write error logs to file with stack trace", async () => {
        const errorMessage = "Test error message";
        const testError = new Error("Source error");
        
        logger.error("system", errorMessage, testError);

        // Polling for file write instead of fixed timeout
        let content = "";
        for (let i = 0; i < 20; i++) {
            if (existsSync(logFile)) {
                content = readFileSync(logFile, "utf-8");
                if (content.includes("Source error")) break;
            }
            await new Promise(resolve => setTimeout(resolve, 50));
        }

        expect(content).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[ERROR\] \[SYSTEM\] Test error message: Source error/);
        expect(content).toContain("Stack Trace:");
        expect(content).toContain("Error: Source error");
        expect(content).toContain("logger.test.ts");
    });

    test("should handle log directory creation failures gracefully", async () => {
        const consoleSpy = spyOn(console, "error");
        
        // We trigger an error by trying to use a path that is a file where a directory should be
        const triggerFile = join(process.cwd(), "logs_fail_trigger");
        
        try {
            writeFileSync(triggerFile, "not a directory");
            
            // Manually override paths for this test instance
            const originalLogDir = (logger as any).logDir;
            const originalLogPath = (logger as any).errorLogPath;
            
            (logger as any).logDir = triggerFile;
            (logger as any).errorLogPath = join(triggerFile, "error.log");
            (logger as any).initialized = false;

            logger.error("system", "This should fail directory creation");
            
            // Wait for async initialization attempt
            await new Promise(resolve => setTimeout(resolve, 100));
            
            expect(consoleSpy).toHaveBeenCalled();
            expect(consoleSpy.mock.calls.some(call => 
                String(call[0]).includes("Failed to initialize logger directory")
            )).toBe(true);

            // Reset logger state
            (logger as any).logDir = originalLogDir;
            (logger as any).errorLogPath = originalLogPath;
            (logger as any).initialized = false;
        } finally {
            if (existsSync(triggerFile)) unlinkSync(triggerFile);
            consoleSpy.mockRestore();
        }
    });

    test("should include complex data objects in logs", () => {
        const spy = spyOn(console, "log");
        const data = { userId: "123", tags: ["test"] };
        logger.info("bot", "Message with data", data);
        
        expect(spy).toHaveBeenCalled();
        const callArgs = spy.mock.calls[0]?.[0];
        expect(callArgs).toBeDefined();
        if (callArgs) {
            expect(callArgs).toContain(` | Data: ${JSON.stringify(data)}`);
        }
        spy.mockRestore();
    });

    test("should handle circular references in data objects", () => {
        const spy = spyOn(console, "log");
        const data: any = { name: "circular" };
        data.self = data;
        
        logger.info("bot", "Circular test", data);
        
        expect(spy).toHaveBeenCalled();
        const callArgs = spy.mock.calls[0]?.[0];
        expect(callArgs).toContain("[Circular]");
        spy.mockRestore();
    });
});
