import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../utils/logger.js", () => ({
    logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
vi.mock("../../db/database.js", () => ({
    closeDatabase: vi.fn(),
}));
vi.mock("../../utils/pid.js", () => ({
    removePidFile: vi.fn().mockResolvedValue(undefined),
}));
import { registerShutdownHandler, clearShutdownHandlers, gracefulShutdown, isShutdownInProgress, trackInflight, getInflightCount, } from "../shutdown.js";
import { closeDatabase } from "../../db/database.js";
import { removePidFile } from "../../utils/pid.js";
describe("shutdown orchestrator", () => {
    beforeEach(() => {
        vi.useFakeTimers();
    });
    afterEach(() => {
        clearShutdownHandlers();
        vi.useRealTimers();
    });
    it("should stop accepting new requests by setting shutdown flag", async () => {
        expect(isShutdownInProgress()).toBe(false);
        const p = gracefulShutdown();
        expect(isShutdownInProgress()).toBe(true);
        await vi.runAllTimersAsync();
        await p;
    });
    it("should run registered handlers in reverse order (LIFO)", async () => {
        const order = [];
        registerShutdownHandler("first", () => {
            order.push("first");
        });
        registerShutdownHandler("second", () => {
            order.push("second");
        });
        registerShutdownHandler("third", () => {
            order.push("third");
        });
        const p = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p;
        expect(order).toEqual(["third", "second", "first"]);
    });
    it("should close database and remove PID file", async () => {
        const p = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p;
        expect(closeDatabase).toHaveBeenCalled();
        expect(removePidFile).toHaveBeenCalled();
    });
    it("should wait for in-flight requests before running handlers", async () => {
        const events = [];
        const release = trackInflight();
        expect(getInflightCount()).toBe(1);
        registerShutdownHandler("handler", () => {
            events.push("handler-ran");
        });
        const p = gracefulShutdown();
        // Advance time a little — handler should NOT have run yet
        await vi.advanceTimersByTimeAsync(100);
        expect(events).toEqual([]);
        // Release the in-flight request
        release();
        expect(getInflightCount()).toBe(0);
        // Now advance past all timers to complete shutdown
        await vi.advanceTimersByTimeAsync(100);
        await vi.runAllTimersAsync();
        await p;
        expect(events).toEqual(["handler-ran"]);
    });
    it("should drain multiple in-flight requests", async () => {
        const release1 = trackInflight();
        const release2 = trackInflight();
        const release3 = trackInflight();
        expect(getInflightCount()).toBe(3);
        const p = gracefulShutdown();
        release1();
        release2();
        expect(getInflightCount()).toBe(1);
        release3();
        expect(getInflightCount()).toBe(0);
        await vi.runAllTimersAsync();
        await p;
    });
    it("should timeout in-flight drain and proceed with shutdown", async () => {
        // Start an in-flight request that never completes
        trackInflight();
        expect(getInflightCount()).toBe(1);
        registerShutdownHandler("handler", vi.fn());
        const p = gracefulShutdown();
        // Advance past the in-flight drain timeout (3 seconds)
        await vi.advanceTimersByTimeAsync(3_000);
        // Advance past the overall shutdown timeout to fully complete
        await vi.advanceTimersByTimeAsync(5_000);
        await p;
        // Shutdown should have completed despite the stuck in-flight request
        expect(closeDatabase).toHaveBeenCalled();
        expect(removePidFile).toHaveBeenCalled();
    });
    it("should close terminal sessions via registered handler", async () => {
        const killAllTerminals = vi.fn();
        registerShutdownHandler("terminal sessions", killAllTerminals);
        const p = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p;
        expect(killAllTerminals).toHaveBeenCalled();
    });
    it("should terminate OpenCode gracefully via registered handler", async () => {
        const stopOpenCode = vi.fn().mockResolvedValue(undefined);
        registerShutdownHandler("opencode process", stopOpenCode);
        const p = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p;
        expect(stopOpenCode).toHaveBeenCalled();
    });
    it("should remove PID file during shutdown", async () => {
        const p = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p;
        expect(removePidFile).toHaveBeenCalled();
    });
    it("should force complete after overall timeout", async () => {
        // Register a handler that takes too long
        registerShutdownHandler("slow handler", async () => {
            await new Promise((resolve) => setTimeout(resolve, 10_000));
        });
        const p = gracefulShutdown();
        // Advance past the overall shutdown timeout (5 seconds)
        await vi.advanceTimersByTimeAsync(5_000);
        await p;
        // Should still clean up final resources even after timeout
        expect(closeDatabase).toHaveBeenCalled();
        expect(removePidFile).toHaveBeenCalled();
    });
    it("should be idempotent — second call is a no-op", async () => {
        const handler = vi.fn();
        registerShutdownHandler("handler", handler);
        const p1 = gracefulShutdown();
        const p2 = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p1;
        await p2;
        expect(handler).toHaveBeenCalledTimes(1);
    });
    it("should continue shutdown even if a handler throws", async () => {
        const events = [];
        registerShutdownHandler("first", () => {
            events.push("first");
        });
        registerShutdownHandler("failing", () => {
            throw new Error("handler failure");
        });
        registerShutdownHandler("third", () => {
            events.push("third");
        });
        const p = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p;
        // Third runs first (LIFO), then failing throws, then first runs
        expect(events).toEqual(["third", "first"]);
        expect(closeDatabase).toHaveBeenCalled();
    });
    it("should handle trackInflight release being called multiple times", () => {
        const release = trackInflight();
        expect(getInflightCount()).toBe(1);
        release();
        expect(getInflightCount()).toBe(0);
        release(); // Double release should be safe
        expect(getInflightCount()).toBe(0);
    });
    it("should exit with code 0 on success (handlers complete normally)", async () => {
        registerShutdownHandler("quick", vi.fn());
        const p = gracefulShutdown();
        await vi.runAllTimersAsync();
        await p;
        // If gracefulShutdown resolves without throwing, it's a success (code 0 path)
        expect(isShutdownInProgress()).toBe(true);
    });
});
//# sourceMappingURL=shutdown.test.js.map