import { describe, it, expect, mock, beforeEach, spyOn } from "bun:test";
import { UserError } from "@shared/lib/errors";

// --- Mocks ---

const mockDeferReply = mock(() => Promise.resolve());
const mockEditReply = mock(() => Promise.resolve());

const mockInteraction = {
    deferReply: mockDeferReply,
    editReply: mockEditReply,
} as any;

const mockCreateErrorEmbed = mock((msg: string) => ({ description: msg, type: "error" }));

mock.module("./embeds", () => ({
    createErrorEmbed: mockCreateErrorEmbed,
}));

// Import AFTER mocking
const { withCommandErrorHandling } = await import("./commandUtils");

// --- Tests ---

describe("withCommandErrorHandling", () => {
    let consoleErrorSpy: ReturnType<typeof spyOn>;

    beforeEach(() => {
        mockDeferReply.mockClear();
        mockEditReply.mockClear();
        mockCreateErrorEmbed.mockClear();
        consoleErrorSpy = spyOn(console, "error").mockImplementation(() => { });
    });

    it("should always call deferReply", async () => {
        await withCommandErrorHandling(
            mockInteraction,
            async () => "result"
        );

        expect(mockDeferReply).toHaveBeenCalledTimes(1);
    });

    it("should pass ephemeral option to deferReply", async () => {
        await withCommandErrorHandling(
            mockInteraction,
            async () => "result",
            { ephemeral: true }
        );

        expect(mockDeferReply).toHaveBeenCalledWith({ ephemeral: true });
    });

    it("should return the operation result on success", async () => {
        const result = await withCommandErrorHandling(
            mockInteraction,
            async () => ({ data: "test" })
        );

        expect(result).toEqual({ data: "test" });
    });

    it("should call onSuccess with the result", async () => {
        const onSuccess = mock(async (_result: string) => { });

        await withCommandErrorHandling(
            mockInteraction,
            async () => "hello",
            { onSuccess }
        );

        expect(onSuccess).toHaveBeenCalledWith("hello");
    });

    it("should send successMessage when no onSuccess is provided", async () => {
        await withCommandErrorHandling(
            mockInteraction,
            async () => "result",
            { successMessage: "It worked!" }
        );

        expect(mockEditReply).toHaveBeenCalledWith({
            content: "It worked!",
        });
    });

    it("should prefer onSuccess over successMessage", async () => {
        const onSuccess = mock(async (_result: string) => { });

        await withCommandErrorHandling(
            mockInteraction,
            async () => "result",
            { successMessage: "This should not be sent", onSuccess }
        );

        expect(onSuccess).toHaveBeenCalledTimes(1);
        // editReply should NOT have been called with the successMessage
        expect(mockEditReply).not.toHaveBeenCalledWith({
            content: "This should not be sent",
        });
    });

    it("should show error embed for UserError", async () => {
        const result = await withCommandErrorHandling(
            mockInteraction,
            async () => {
                throw new UserError("You can't do that!");
            }
        );

        expect(result).toBeUndefined();
        expect(mockCreateErrorEmbed).toHaveBeenCalledWith("You can't do that!");
        expect(mockEditReply).toHaveBeenCalledTimes(1);
    });

    it("should show generic error and log for unexpected errors", async () => {
        const unexpectedError = new Error("Database exploded");

        const result = await withCommandErrorHandling(
            mockInteraction,
            async () => {
                throw unexpectedError;
            }
        );

        expect(result).toBeUndefined();
        expect(consoleErrorSpy).toHaveBeenCalledWith(
            "Unexpected error in command:",
            unexpectedError
        );
        expect(mockCreateErrorEmbed).toHaveBeenCalledWith(
            "An unexpected error occurred."
        );
        expect(mockEditReply).toHaveBeenCalledTimes(1);
    });

    it("should return undefined on error", async () => {
        const result = await withCommandErrorHandling(
            mockInteraction,
            async () => {
                throw new Error("fail");
            }
        );

        expect(result).toBeUndefined();
    });
});
