import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// ── Mocks ────────────────────────────────────────────────────────────────
vi.mock("../utils/logger.js", () => ({
    logger: {
        debug: vi.fn(),
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
    },
}));
const mockEnsureConfigDirs = vi.fn();
vi.mock("../utils/config.js", () => ({
    ensureConfigDirs: (...args) => mockEnsureConfigDirs(...args),
}));
const mockOpenDatabase = vi.fn();
vi.mock("../db/database.js", () => ({
    openDatabase: (...args) => mockOpenDatabase(...args),
    closeDatabase: vi.fn(),
}));
const mockCleanupOldRuns = vi.fn().mockReturnValue(0);
vi.mock("../db/repositories/command-runs.js", () => ({
    cleanupOldRuns: (...args) => mockCleanupOldRuns(...args),
}));
const mockListProjects = vi.fn().mockReturnValue([]);
vi.mock("../db/repositories/projects.js", () => ({
    listProjects: (...args) => mockListProjects(...args),
}));
const mockControlPlaneStart = vi.fn().mockResolvedValue("http://127.0.0.1:17333");
const mockControlPlaneStop = vi.fn().mockResolvedValue(undefined);
vi.mock("../control-plane/server.js", () => ({
    createControlPlane: () => ({
        start: mockControlPlaneStart,
        stop: mockControlPlaneStop,
        address: "http://127.0.0.1:17333",
    }),
}));
const mockWritePidFile = vi.fn().mockResolvedValue(undefined);
vi.mock("../utils/pid.js", () => ({
    writePidFile: (...args) => mockWritePidFile(...args),
    readPidFile: vi.fn().mockResolvedValue(null),
    removePidFile: vi.fn().mockResolvedValue(undefined),
    isProcessRunning: vi.fn().mockReturnValue(false),
}));
const mockLoadTokens = vi.fn();
vi.mock("../auth/token-storage.js", () => ({
    loadTokens: (...args) => mockLoadTokens(...args),
    saveTokens: vi.fn(),
    clearTokens: vi.fn(),
}));
const mockRefreshTokenIfNeeded = vi.fn().mockResolvedValue(false);
const mockStartTokenMonitor = vi.fn();
const mockStopTokenMonitor = vi.fn();
vi.mock("../auth/token-refresh.js", () => ({
    refreshTokenIfNeeded: (...args) => mockRefreshTokenIfNeeded(...args),
    startTokenMonitor: (...args) => mockStartTokenMonitor(...args),
    stopTokenMonitor: (...args) => mockStopTokenMonitor(...args),
}));
const mockLogin = vi.fn().mockResolvedValue({
    user_id: "user-1",
    email: "test@example.com",
    machine_key: "mk-1",
});
vi.mock("../auth/login.js", () => ({
    login: (...args) => mockLogin(...args),
    setGetOAuthUrlFn: vi.fn(),
}));
// Gateway client mock
const mockGatewayConnect = vi.fn();
const mockGatewayDisconnect = vi.fn();
const mockGatewayOn = vi.fn();
const mockGatewaySend = vi.fn();
vi.mock("../gateway/websocket-client.js", () => {
    return {
        GatewayClient: class {
            connect = mockGatewayConnect;
            disconnect = mockGatewayDisconnect;
            on = mockGatewayOn;
            off = vi.fn();
            send = mockGatewaySend;
            connected = true;
            state = "connected";
        },
    };
});
const mockSendDaemonRegister = vi.fn().mockResolvedValue(true);
vi.mock("../gateway/messages/daemon-register.js", () => ({
    sendDaemonRegister: (...args) => mockSendDaemonRegister(...args),
}));
// Message router mock
const mockRegisterHandler = vi.fn();
const mockRouterAttach = vi.fn();
const mockRouterDetach = vi.fn();
vi.mock("../gateway/message-router.js", () => {
    return {
        MessageRouter: class {
            registerHandler = mockRegisterHandler;
            attach = mockRouterAttach;
            detach = mockRouterDetach;
        },
    };
});
// File watcher mock
vi.mock("../services/file-watcher.js", () => {
    return {
        FileWatcher: class {
            start = vi.fn();
            stop = vi.fn().mockResolvedValue(undefined);
            isWatching = true;
        },
    };
});
// OpenCode mocks
vi.mock("../opencode/spawn.js", () => ({
    spawnOpenCode: vi.fn().mockResolvedValue({
        process: { kill: vi.fn() },
        auth: { username: "user", password: "pass" },
        port: 4096,
    }),
}));
vi.mock("../opencode/proxy.js", () => ({
    setOpenCodeAuth: vi.fn(),
    clearOpenCodeAuth: vi.fn(),
}));
vi.mock("../opencode/supervisor.js", () => ({
    supervise: vi.fn(),
    stopSupervisor: vi.fn(),
}));
vi.mock("../opencode/sse.js", () => ({
    setSseSendFn: vi.fn(),
    startSseSubscription: vi.fn(),
    stopSseSubscription: vi.fn(),
}));
vi.mock("../opencode/health-check.js", () => ({
    startHealthCheck: vi.fn(),
    stopHealthCheck: vi.fn(),
}));
vi.mock("../services/command-runner.js", () => ({
    killAllCommands: vi.fn(),
}));
vi.mock("../services/terminal-manager.js", () => ({
    setTerminalSendFn: vi.fn(),
    killAllTerminals: vi.fn(),
}));
vi.mock("../services/message-queue.js", () => ({
    clearAllQueues: vi.fn(),
}));
const mockRegisterShutdownHandler = vi.fn();
const mockGracefulShutdown = vi.fn().mockResolvedValue(undefined);
vi.mock("../services/shutdown.js", () => ({
    registerShutdownHandler: (...args) => mockRegisterShutdownHandler(...args),
    gracefulShutdown: (...args) => mockGracefulShutdown(...args),
    clearShutdownHandlers: vi.fn(),
}));
// Mock all handlers
vi.mock("../handlers/fs-list.js", () => ({ handleFsList: vi.fn() }));
vi.mock("../handlers/fs-read.js", () => ({ handleFsRead: vi.fn() }));
vi.mock("../handlers/fs-write.js", () => ({ handleFsWrite: vi.fn() }));
vi.mock("../handlers/fs-create.js", () => ({ handleFsCreate: vi.fn() }));
vi.mock("../handlers/fs-delete.js", () => ({ handleFsDelete: vi.fn() }));
vi.mock("../handlers/fs-search.js", () => ({ handleFsSearch: vi.fn() }));
vi.mock("../handlers/fs-replace.js", () => ({ handleFsReplace: vi.fn() }));
vi.mock("../handlers/command-exec.js", () => ({ handleCommandExec: vi.fn() }));
vi.mock("../handlers/command-cancel.js", () => ({ handleCommandCancel: vi.fn() }));
vi.mock("../handlers/git-status.js", () => ({ handleGitStatus: vi.fn() }));
vi.mock("../handlers/git-branches.js", () => ({ handleGitBranches: vi.fn() }));
vi.mock("../handlers/git-branch-create.js", () => ({ handleGitBranchCreate: vi.fn() }));
vi.mock("../handlers/git-checkout.js", () => ({ handleGitCheckout: vi.fn() }));
vi.mock("../handlers/git-stage.js", () => ({ handleGitStage: vi.fn() }));
vi.mock("../handlers/git-unstage.js", () => ({ handleGitUnstage: vi.fn() }));
vi.mock("../handlers/git-commit.js", () => ({ handleGitCommit: vi.fn() }));
vi.mock("../handlers/git-push.js", () => ({ handleGitPush: vi.fn() }));
vi.mock("../handlers/git-pull.js", () => ({ handleGitPull: vi.fn() }));
vi.mock("../handlers/git-file-base.js", () => ({ handleGitFileBase: vi.fn() }));
vi.mock("../handlers/git-discard.js", () => ({ handleGitDiscard: vi.fn() }));
vi.mock("../handlers/git-diff.js", () => ({ handleGitDiff: vi.fn() }));
vi.mock("../handlers/agent-sessions-list.js", () => ({ handleAgentSessionsList: vi.fn() }));
vi.mock("../handlers/agent-session-new.js", () => ({ handleAgentSessionNew: vi.fn() }));
vi.mock("../handlers/agent-message.js", () => ({ handleAgentMessage: vi.fn() }));
vi.mock("../handlers/agent-cancel.js", () => ({ handleAgentCancel: vi.fn() }));
vi.mock("../handlers/agent-prompt-response.js", () => ({ handleAgentPromptResponse: vi.fn() }));
vi.mock("../handlers/machines-list.js", () => ({
    handleMachinesList: vi.fn(),
    setMachinesListGatewayClient: vi.fn(),
}));
vi.mock("../handlers/projects-list.js", () => ({ handleProjectsList: vi.fn() }));
vi.mock("../handlers/project-create.js", () => ({
    handleProjectCreate: vi.fn(),
    setProjectCreateGatewayClient: vi.fn(),
    setProjectCreateStartWatcher: vi.fn(),
}));
vi.mock("../handlers/project-delete.js", () => ({
    handleProjectDelete: vi.fn(),
    setProjectDeleteGatewayClient: vi.fn(),
    setProjectDeleteStopWatcher: vi.fn(),
}));
vi.mock("../handlers/terminal-create.js", () => ({ handleTerminalCreate: vi.fn() }));
vi.mock("../handlers/terminal-stdin.js", () => ({ handleTerminalStdin: vi.fn() }));
vi.mock("../handlers/terminal-resize.js", () => ({ handleTerminalResize: vi.fn() }));
vi.mock("../handlers/terminal-kill.js", () => ({ handleTerminalKill: vi.fn() }));
vi.mock("../opencode/permission-reply.js", () => ({ handlePermissionReply: vi.fn() }));
const mockCheckOpenCodeInstalled = vi.fn().mockResolvedValue({ available: false });
vi.mock("../opencode/check-installed.js", () => ({
    checkOpenCodeInstalled: (...args) => mockCheckOpenCodeInstalled(...args),
}));
const mockSetOpenCodeCheckResult = vi.fn();
vi.mock("../services/capabilities.js", () => ({
    setOpenCodeCheckResult: (...args) => mockSetOpenCodeCheckResult(...args),
}));
const mockGetEnv = vi.fn().mockReturnValue({ gatewayUrl: "wss://pocketcoder-gateway.fly.dev" });
vi.mock("../env.js", () => ({
    getEnv: (...args) => mockGetEnv(...args),
}));
// ── Tests ────────────────────────────────────────────────────────────────
import { startDaemon } from "../daemon.js";
import { logger } from "../utils/logger.js";
const validTokens = {
    machine_key: "mk-1",
    access_token: "at-1",
    refresh_token: "rt-1",
    expires_at: Math.floor(Date.now() / 1000) + 3600,
    user_id: "user-1",
};
describe("daemon startup", () => {
    beforeEach(() => {
        vi.clearAllMocks();
        // Default: tokens exist and are valid
        mockLoadTokens.mockResolvedValue(validTokens);
    });
    afterEach(() => {
        // Remove signal listeners added by setupSignalHandlers
        process.removeAllListeners("SIGTERM");
        process.removeAllListeners("SIGINT");
    });
    it("should initialize config directories", async () => {
        await startDaemon();
        expect(mockEnsureConfigDirs).toHaveBeenCalledOnce();
    });
    it("should initialize database", async () => {
        await startDaemon();
        expect(mockOpenDatabase).toHaveBeenCalledOnce();
    });
    it("should run command_runs cleanup", async () => {
        mockCleanupOldRuns.mockReturnValue(5);
        await startDaemon();
        expect(mockCleanupOldRuns).toHaveBeenCalledOnce();
        expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Cleaned up 5"));
    });
    it("should start control plane", async () => {
        await startDaemon();
        expect(mockControlPlaneStart).toHaveBeenCalledOnce();
    });
    it("should write PID file", async () => {
        await startDaemon();
        expect(mockWritePidFile).toHaveBeenCalledOnce();
    });
    it("should throw if port is busy (daemon already running)", async () => {
        const err = new Error("listen EADDRINUSE: address already in use 127.0.0.1:17333");
        mockControlPlaneStart.mockRejectedValueOnce(err);
        await expect(startDaemon()).rejects.toThrow("Daemon already running");
    });
    it("should check authentication", async () => {
        await startDaemon();
        expect(mockLoadTokens).toHaveBeenCalled();
        expect(mockRefreshTokenIfNeeded).toHaveBeenCalled();
    });
    it("should trigger login when no tokens exist", async () => {
        // First call: no tokens, second call (after login): tokens exist
        mockLoadTokens.mockResolvedValueOnce(null).mockResolvedValueOnce(validTokens);
        await startDaemon();
        expect(mockLogin).toHaveBeenCalledOnce();
    });
    it("should trigger login when token refresh fails", async () => {
        mockRefreshTokenIfNeeded.mockRejectedValueOnce(new Error("refresh failed"));
        // After login: tokens exist
        mockLoadTokens.mockResolvedValueOnce(validTokens).mockResolvedValueOnce(validTokens);
        await startDaemon();
        expect(mockLogin).toHaveBeenCalledOnce();
    });
    it("should connect to gateway on valid auth", async () => {
        await startDaemon();
        expect(mockGatewayConnect).toHaveBeenCalledOnce();
    });
    it("should use GATEWAY_URL from getEnv()", async () => {
        mockGetEnv.mockReturnValue({ gatewayUrl: "wss://custom-gateway.example.com" });
        await startDaemon();
        expect(mockGatewayConnect).toHaveBeenCalled();
        expect(mockGetEnv).toHaveBeenCalled();
    });
    it("should register message handlers", async () => {
        await startDaemon();
        // Should register 26 handlers (all message types)
        expect(mockRegisterHandler).toHaveBeenCalledTimes(35);
        expect(mockRouterAttach).toHaveBeenCalledOnce();
    });
    it("should start file watchers for registered projects", async () => {
        mockListProjects.mockReturnValue([
            { project_id: "p1", name: "Project 1", path: "/projects/p1" },
            { project_id: "p2", name: "Project 2", path: "/projects/p2" },
        ]);
        await startDaemon();
        expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('File watcher started for project "Project 1"'));
        expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('File watcher started for project "Project 2"'));
    });
    it("should register shutdown handlers", async () => {
        // Mock OpenCode as available so all shutdown handlers register
        mockCheckOpenCodeInstalled.mockResolvedValue({ available: true });
        await startDaemon();
        // Should register: control plane, token monitor, file watchers, message queues,
        // opencode (health check, sse, supervisor), running commands, terminal sessions, gateway connection
        expect(mockRegisterShutdownHandler).toHaveBeenCalledTimes(10);
        const names = mockRegisterShutdownHandler.mock.calls.map((call) => call[0]);
        expect(names).toContain("control plane");
        expect(names).toContain("token monitor");
        expect(names).toContain("file watchers");
        expect(names).toContain("gateway connection");
        expect(names).toContain("opencode health check");
        expect(names).toContain("opencode sse");
        expect(names).toContain("opencode supervisor");
        expect(names).toContain("running commands");
        expect(names).toContain("terminal sessions");
        expect(names).toContain("message queues");
    });
    it("should log ready message", async () => {
        await startDaemon();
        expect(logger.info).toHaveBeenCalledWith("Daemon ready. Connected to gateway.");
    });
    it("should shutdown gracefully on SIGTERM", async () => {
        await startDaemon();
        // Simulate SIGTERM - use emit to trigger the handler without exiting
        const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => undefined);
        process.emit("SIGTERM", "SIGTERM");
        // Wait for async shutdown
        await vi.waitFor(() => {
            expect(mockGracefulShutdown).toHaveBeenCalledOnce();
        });
        exitSpy.mockRestore();
    });
});
//# sourceMappingURL=daemon-startup.test.js.map