import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("../../../utils/pid.js", () => ({
    readPidFile: vi.fn(),
    isProcessRunning: vi.fn(),
}));
// Mock child_process.spawn for daemonize test
vi.mock("node:child_process", () => ({
    spawn: vi.fn(),
}));
// Mock token storage for auth check
vi.mock("../../../auth/token-storage.js", () => ({
    loadTokens: vi.fn(),
}));
// Mock login module
vi.mock("../../../auth/login.js", () => ({
    login: vi.fn(),
    setGetOAuthUrlFn: vi.fn(),
}));
// Mock env module
vi.mock("../../../env.js", () => ({
    getEnv: vi.fn(() => ({ supabaseUrl: "https://test.supabase.co", gatewayUrl: "wss://test" })),
}));
import { readPidFile, isProcessRunning } from "../../../utils/pid.js";
import { spawn } from "node:child_process";
import { loadTokens } from "../../../auth/token-storage.js";
import { login } from "../../../auth/login.js";
import { startCommand, isDaemonRunning } from "../start.js";
import { stopCommand } from "../stop.js";
const mockReadPidFile = vi.mocked(readPidFile);
const mockIsProcessRunning = vi.mocked(isProcessRunning);
const mockSpawn = vi.mocked(spawn);
const mockLoadTokens = vi.mocked(loadTokens);
const mockLogin = vi.mocked(login);
describe("start command", () => {
    beforeEach(() => {
        vi.restoreAllMocks();
        vi.useFakeTimers();
        mockSpawn.mockReset();
        mockLoadTokens.mockReset();
        mockLogin.mockReset();
        process.exitCode = undefined;
        // Reset fetch mock
        globalThis.fetch = vi.fn();
    });
    afterEach(() => {
        vi.useRealTimers();
    });
    it("should check for existing daemon and wait for ready", async () => {
        // Mock existing tokens (user already logged in)
        mockLoadTokens.mockResolvedValue({
            machine_key: "test-key",
            access_token: "test-access",
            refresh_token: "test-refresh",
            expires_at: Math.floor(Date.now() / 1000) + 3600,
            user_id: "test-user",
        });
        // Mock spawn for daemonize
        mockSpawn.mockReturnValue({
            pid: 12345,
            unref: vi.fn(),
        });
        // After spawn, process is running
        mockIsProcessRunning.mockReturnValue(true);
        // First call: no PID file (daemon not running)
        // Subsequent calls: return spawned PID (daemon wrote PID file)
        let pidCallCount = 0;
        mockReadPidFile.mockImplementation(async () => {
            pidCallCount++;
            return pidCallCount === 1 ? null : 12345;
        });
        // Mock healthz: first call throws (not running), subsequent calls succeed
        let healthzCallCount = 0;
        vi.mocked(globalThis.fetch).mockImplementation(async () => {
            healthzCallCount++;
            if (healthzCallCount === 1) {
                throw new Error("Connection refused");
            }
            return {
                ok: true,
                json: () => Promise.resolve({ status: "ok" }),
            };
        });
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        // Start the command and run all timers to completion
        const commandPromise = startCommand({});
        await vi.runAllTimersAsync();
        await commandPromise;
        expect(mockReadPidFile).toHaveBeenCalled();
        expect(consoleSpy).toHaveBeenCalledWith("Daemon started (PID: 12345)");
        consoleSpy.mockRestore();
    });
    it("should run login flow when no tokens exist", async () => {
        // No existing tokens - login needed
        mockLoadTokens.mockResolvedValue(null);
        // Mock successful login
        mockLogin.mockResolvedValue({
            user_id: "test-user",
            email: "test@example.com",
            machine_key: "test-key",
        });
        // Mock spawn for daemonize
        mockSpawn.mockReturnValue({
            pid: 12345,
            unref: vi.fn(),
        });
        // After spawn, process is running
        mockIsProcessRunning.mockReturnValue(true);
        // First call: no PID file (daemon not running)
        // Subsequent calls: return spawned PID (daemon wrote PID file)
        let pidCallCount = 0;
        mockReadPidFile.mockImplementation(async () => {
            pidCallCount++;
            return pidCallCount === 1 ? null : 12345;
        });
        // Mock healthz: fails initially, succeeds after spawn
        let healthzCallCount = 0;
        vi.mocked(globalThis.fetch).mockImplementation(async () => {
            healthzCallCount++;
            if (healthzCallCount === 1) {
                throw new Error("Connection refused");
            }
            return {
                ok: true,
                json: () => Promise.resolve({ status: "ok" }),
            };
        });
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        // Start the command and run all timers to completion
        const commandPromise = startCommand({});
        await vi.runAllTimersAsync();
        await commandPromise;
        expect(consoleSpy).toHaveBeenCalledWith("No session found. Starting login...");
        expect(consoleSpy).toHaveBeenCalledWith("Opening browser for GitHub authentication in 5 seconds...");
        expect(mockLogin).toHaveBeenCalled();
        expect(consoleSpy).toHaveBeenCalledWith("Logged in as test@example.com");
        expect(consoleSpy).toHaveBeenCalledWith("Daemon started (PID: 12345)");
        consoleSpy.mockRestore();
    });
    it("should report failure when daemon process exits", async () => {
        // No PID file means daemon is not running
        mockReadPidFile.mockResolvedValue(null);
        // Mock existing tokens
        mockLoadTokens.mockResolvedValue({
            machine_key: "test-key",
            access_token: "test-access",
            refresh_token: "test-refresh",
            expires_at: Math.floor(Date.now() / 1000) + 3600,
            user_id: "test-user",
        });
        // Mock spawn for daemonize
        mockSpawn.mockReturnValue({
            pid: 12345,
            unref: vi.fn(),
        });
        // Process exits immediately after spawn
        mockIsProcessRunning.mockReturnValue(false);
        // Mock healthz: initial check fails
        vi.mocked(globalThis.fetch).mockRejectedValue(new Error("Connection refused"));
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
        // Start the command and run all timers to completion
        const commandPromise = startCommand({});
        await vi.runAllTimersAsync();
        await commandPromise;
        expect(consoleErrorSpy).toHaveBeenCalledWith("Daemon failed to start (process exited)");
        expect(consoleErrorSpy).toHaveBeenCalledWith("Run 'pocketcoder logs' for details");
        expect(process.exitCode).toBe(1);
        consoleSpy.mockRestore();
        consoleErrorSpy.mockRestore();
    });
    it("should handle login failure gracefully", async () => {
        // No PID file means daemon is not running
        mockReadPidFile.mockResolvedValue(null);
        // No existing tokens - login needed
        mockLoadTokens.mockResolvedValue(null);
        // Mock failed login
        mockLogin.mockRejectedValue(new Error("OAuth timeout"));
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
        // Start the command and run all timers to completion
        const commandPromise = startCommand({});
        await vi.runAllTimersAsync();
        await commandPromise;
        expect(consoleErrorSpy).toHaveBeenCalledWith("Login failed: OAuth timeout");
        expect(process.exitCode).toBe(1);
        expect(mockSpawn).not.toHaveBeenCalled(); // Should not spawn daemon on login failure
        consoleSpy.mockRestore();
        consoleErrorSpy.mockRestore();
    });
    it("should not start if daemon is already running", async () => {
        mockReadPidFile.mockResolvedValue(9999);
        mockIsProcessRunning.mockReturnValue(true);
        vi.mocked(globalThis.fetch).mockResolvedValue({
            ok: true,
            json: () => Promise.resolve({ status: "ok" }),
        });
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        await startCommand({});
        expect(consoleSpy).toHaveBeenCalledWith("Daemon is already running");
        expect(mockSpawn).not.toHaveBeenCalled();
        consoleSpy.mockRestore();
    });
    it("should start in foreground mode", async () => {
        mockReadPidFile.mockResolvedValue(null);
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
        // startDaemon doesn't exist yet, so the dynamic import will fail
        await startCommand({ foreground: true });
        expect(consoleSpy).toHaveBeenCalledWith("Starting daemon in foreground...");
        // The import will fail since daemon.ts doesn't exist yet — that's expected
        expect(consoleErrorSpy).toHaveBeenCalled();
        consoleSpy.mockRestore();
        consoleErrorSpy.mockRestore();
    });
});
describe("isDaemonRunning", () => {
    beforeEach(() => {
        vi.restoreAllMocks();
        globalThis.fetch = vi.fn();
    });
    it("should return false when no PID file", async () => {
        mockReadPidFile.mockResolvedValue(null);
        expect(await isDaemonRunning()).toBe(false);
    });
    it("should return false when process not running", async () => {
        mockReadPidFile.mockResolvedValue(9999);
        mockIsProcessRunning.mockReturnValue(false);
        expect(await isDaemonRunning()).toBe(false);
    });
    it("should return true when process running and healthz ok", async () => {
        mockReadPidFile.mockResolvedValue(9999);
        mockIsProcessRunning.mockReturnValue(true);
        vi.mocked(globalThis.fetch).mockResolvedValue({
            ok: true,
            json: () => Promise.resolve({ status: "ok" }),
        });
        expect(await isDaemonRunning()).toBe(true);
    });
    it("should return false when healthz fails", async () => {
        mockReadPidFile.mockResolvedValue(9999);
        mockIsProcessRunning.mockReturnValue(true);
        vi.mocked(globalThis.fetch).mockRejectedValue(new Error("Connection refused"));
        expect(await isDaemonRunning()).toBe(false);
    });
});
describe("stop command", () => {
    beforeEach(() => {
        vi.restoreAllMocks();
        process.exitCode = undefined;
        globalThis.fetch = vi.fn();
    });
    it("should call stop endpoint", async () => {
        // First call: isDaemonRunning check (healthz)
        // Second call: POST /stop
        mockReadPidFile.mockResolvedValue(9999);
        mockIsProcessRunning.mockReturnValue(true);
        vi.mocked(globalThis.fetch)
            .mockResolvedValueOnce({
            ok: true,
            json: () => Promise.resolve({ status: "ok" }),
        })
            .mockResolvedValueOnce({
            ok: true,
            json: () => Promise.resolve({ status: "stopping" }),
        });
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        await stopCommand();
        expect(vi.mocked(globalThis.fetch)).toHaveBeenCalledTimes(2);
        expect(consoleSpy).toHaveBeenCalledWith("Daemon is stopping...");
        consoleSpy.mockRestore();
    });
    it("should report daemon not running", async () => {
        mockReadPidFile.mockResolvedValue(null);
        const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
        await stopCommand();
        expect(consoleSpy).toHaveBeenCalledWith("Daemon is not running");
        consoleSpy.mockRestore();
    });
    it("should handle stop endpoint failure", async () => {
        mockReadPidFile.mockResolvedValue(9999);
        mockIsProcessRunning.mockReturnValue(true);
        vi.mocked(globalThis.fetch)
            .mockResolvedValueOnce({
            ok: true,
            json: () => Promise.resolve({ status: "ok" }),
        })
            .mockRejectedValueOnce(new Error("Connection reset"));
        const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
        await stopCommand();
        expect(consoleSpy).toHaveBeenCalledWith("Failed to stop daemon: Connection reset");
        expect(process.exitCode).toBe(1);
        consoleSpy.mockRestore();
    });
});
//# sourceMappingURL=cli-daemon.test.js.map