BareGit
#pragma once

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <deque>
#include <filesystem>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "game_record_store.hpp"
#include "game_session.hpp"
#include "server_config.hpp"

namespace nethack_mcp
{

/// Own the active-game registry and route agent calls to isolated sessions.
class GameManager
{
public:
    /// Open the record store, recover old rows, and start the expiry timer.
    static mw::E<std::unique_ptr<GameManager>> create(
        ServerConfig config, std::filesystem::path runtime_source);

    ~GameManager();

    GameManager(const GameManager&) = delete;
    GameManager& operator=(const GameManager&) = delete;

    /// Create and admit a new game, returning its secret once to the caller.
    ToolResult createGame(const Json& arguments, const std::string& client_id);

    /// Authorize and dispatch one game-specific MCP tool call.
    ToolResult dispatch(const std::string& tool_name, const Json& arguments,
                        const std::string& client_id);

    /// Return a shared active-game handle for read-only spectator access.
    std::shared_ptr<GameSession> findActive(const std::string& game_id) const;

    /// Return a durable record for a valid game ID, if one exists.
    mw::E<std::optional<GameRecord>> getRecord(const std::string& game_id);

    /// Return the ten most recently completed records.
    mw::E<std::vector<GameRecord>> recentGames();

    /// Stop expiry maintenance and terminate workers during server shutdown.
    void shutdown();

    /// Return the canonical public base URL used in generated viewer links.
    const std::string& publicBaseUrl() const;

    /// Return the configured game-independent HTTP service settings.
    const ServerConfig& config() const;

    /// Return the number of sessions currently present in the registry.
    std::size_t activeGameCount() const;

    /// Count worker child processes that are still running.
    std::size_t activeWorkerCount() const;

    /// Count files under the private runtime root for capacity monitoring.
    std::uint64_t runtimeBytes() const;

    /// Return cleanup failures seen by the expiry maintenance thread.
    std::uint64_t expiryCleanupFailures() const;

    /// Return latency of the most recent SQLite write in microseconds.
    std::uint64_t databaseWriteLatencyMicroseconds() const;

private:
    GameManager(ServerConfig config,
                std::filesystem::path runtime_source,
                std::shared_ptr<GameRecordStore> records);

    void sweepLoop();
    void removeOrphanedDirectories();
    void releaseReservation(const std::string& game_id);
    bool allowCreation(const std::string& client_id,
                       std::chrono::steady_clock::time_point now);
    bool allowControlAttempt(const std::string& client_id);
    void recordControlFailure(const std::string& client_id);
    void clearControlFailures(const std::string& client_id);
    void pruneClientHistory(std::chrono::steady_clock::time_point now);

    struct ClientHistory
    {
        std::deque<std::chrono::steady_clock::time_point> creation_times;
        std::deque<std::chrono::steady_clock::time_point> auth_failures;
    };

    ServerConfig config_;
    std::filesystem::path runtime_source_;
    std::shared_ptr<GameRecordStore> records_;
    mutable std::mutex registry_mutex_;
    std::unordered_map<std::string, std::shared_ptr<GameSession>> sessions_;
    std::unordered_set<std::string> reserved_ids_;
    std::size_t pending_creations_ = 0;
    std::unordered_map<std::string, ClientHistory> client_history_;
    std::mutex sweep_mutex_;
    std::condition_variable sweep_condition_;
    std::atomic<bool> stopping_ = false;
    std::atomic<std::uint64_t> expiry_cleanup_failures_ = 0;
    std::thread sweep_thread_;
};

} // namespace nethack_mcp