/*
 * server/session_registry.h — In-memory session, channel, and user registry.
 *
 * Tracks all authenticated sessions, the channel tree, user<→>channel assignments,
 * UDP endpoint bindings (M2), and SSRC<→>session mappings (M2).
 * Protected by a shared_mutex (many readers, few writers). All methods are thread-safe.
 */
#ifndef VOICECAT_SERVER_SESSION_REGISTRY_H
#define VOICECAT_SERVER_SESSION_REGISTRY_H

#ifdef VOICECAT_HAS_NET

#include <array>
#include <atomic>
#include <cstdint>
#include <memory>
#include <optional>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>

#define ASIO_STANDALONE 1
#include <asio.hpp>

#include "db.h"
#include "proto/voicecat.pb.h"

namespace voicecat::server {

class ConnSession;

struct ChannelEntry {
    voicecat::v1::Channel proto;
};

struct UserEntry {
    voicecat::v1::User proto;
    uint64_t session_id{};
};

// Hashes asio::ip::udp::endpoint by "addr:port" string.
struct UdpEndpointHash {
    size_t operator()(const asio::ip::udp::endpoint& ep) const {
        std::string key = ep.address().to_string() + ':' + std::to_string(ep.port());
        return std::hash<std::string>{}(key);
    }
};

class SessionRegistry {
   public:
    explicit SessionRegistry(std::shared_ptr<Database> db);

    // Load channels from the database, seeding defaults on first run.
    void load_channels();

    // Register a session (before auth). Returns the assigned session_id.
    uint64_t register_session(std::weak_ptr<ConnSession> session);

    // Remove a session (called on disconnect).
    void unregister_session(uint64_t session_id);

    // Add a user once authenticated. Returns the assigned user_id.
    uint32_t add_user(uint64_t session_id, const voicecat::v1::User& user);

    // Remove a user (called on disconnect after auth).
    void remove_user(uint32_t user_id);

    // Broadcast a UserEvent::LEFT for a user to all other sessions. Called by
    // ConnSession::close() before remove_user() so remaining clients learn about an
    // ungraceful disconnect (TCP drop, crash, network loss). Mirrors the first half of
    // kick_user(). Takes the shared lock internally; safe to call from ConnSession::close.
    void broadcast_left(uint32_t user_id, const std::string& reason);

    // Move a user to a channel. Returns false if channel doesn't exist.
    bool set_user_channel(uint32_t user_id, uint32_t channel_id);

    // Snapshot for ServerStateSnapshot message.
    std::vector<voicecat::v1::Channel> channel_snapshot() const;
    std::vector<voicecat::v1::User>    user_snapshot() const;
    std::optional<voicecat::v1::User>  user_snapshot_user(uint32_t user_id) const;
    std::optional<std::string>         user_nickname(uint32_t user_id) const;

    // Resolve target sessions for a text message relay.
    std::vector<std::shared_ptr<ConnSession>> resolve_text_targets(
        uint64_t sender_session_id, voicecat::v1::TextScope scope, uint32_t target_id) const;

    // Broadcast an envelope to all sessions except the excluded one.
    void broadcast(const voicecat::v1::Envelope& env, uint64_t exclude_session_id = 0) const;

    // Return all sessions whose last_seen is older than max_age_ms (steady_clock ms), i.e.
    // have not had any inbound TCP or UDP activity in that span. The reaper (server.cpp)
    // calls close() on each — which broadcasts UserEvent::LEFT via the Tier 1 fix. Locks
    // only to collect the list; close() runs outside the lock (mirrors kick_user's pattern).
    std::vector<std::shared_ptr<ConnSession>> find_stale_sessions(int64_t max_age_ms) const;

   private:
    void broadcast_unlocked(const voicecat::v1::Envelope& env,
                            uint64_t exclude_session_id = 0) const;

   public:
    // ── Permissions ────────────────────────────────────────────────────────────

    void set_session_permissions(uint64_t session_id,
                                 const voicecat::v1::Permissions& perms);
    std::optional<voicecat::v1::Permissions> get_session_permissions(
        uint64_t session_id) const;

    // ── Moderation ─────────────────────────────────────────────────────────────

    // Find a live session by its user_id. Returns nullptr if offline.
    std::shared_ptr<ConnSession> find_session_by_user_id(uint32_t user_id) const;

    // Forcibly disconnect a user with a reason. Broadcasts UserEvent::LEFT.
    // Returns true if the user was online.
    bool kick_user(uint32_t user_id, const std::string& reason);

    // Kick a user and insert a persistent ban. Returns true if the user was online.
    bool ban_user(uint32_t user_id, const std::string& reason, int64_t expires_at);

    // Set server-mute/deafen flags on a user and broadcast the update.
    bool set_server_mute(uint32_t user_id, bool muted, bool deafened);

    // Move a user to a channel (permission-checked by caller).
    bool move_user(uint32_t user_id, uint32_t channel_id);

    // ── Channel CRUD ───────────────────────────────────────────────────────────

    // Create a channel. Returns the new channel id, or 0 on error.
    uint32_t create_channel(const voicecat::v1::Channel& ch, const std::string& password,
                            std::string& error);

    // Update a channel. Returns false on error.
    bool update_channel(const voicecat::v1::Channel& ch, const std::string& password,
                        std::string& error);

    // Delete a channel. Remaining users are moved to Lobby (id=1). Returns false on error.
    bool delete_channel(uint32_t channel_id, std::string& error);

    // Return a channel proto by id, or nullopt.
    std::optional<voicecat::v1::Channel> get_channel(uint32_t channel_id) const;

    // Check a channel password.
    bool check_channel_password(uint32_t channel_id, const std::string& password) const;

    // ── M2: UDP / media ────────────────────────────────────────────────────────

    // Register a session's UDP token (called at auth success).
    void register_udp_token(const std::array<uint8_t, 16>& token, uint64_t session_id);

    // Locate a session by its UDP binding token (called by MediaRelay on UDP_BINDING).
    std::shared_ptr<ConnSession> find_by_udp_token(const std::array<uint8_t, 16>& token) const;

    // Associate a UDP endpoint with a session (called by MediaRelay after token verification).
    void register_udp_endpoint(asio::ip::udp::endpoint ep, uint64_t session_id);

    // Locate the session that owns a UDP sender endpoint (called per incoming voice packet).
    std::shared_ptr<ConnSession> find_by_udp_endpoint(const asio::ip::udp::endpoint& ep) const;

    // Assign an SSRC for a new stream. Returns the assigned SSRC.
    uint32_t assign_ssrc(uint64_t session_id);

    // Add/replace a stream entry on a user (called when StreamAnnounce succeeds).
    // Returns the updated User proto for broadcasting, or nullopt if user not found.
    std::optional<voicecat::v1::User> set_user_stream(uint32_t user_id,
                                                         const voicecat::v1::StreamInfo& info);

    // Remove a stream entry from a user (called on StreamStop). Returns the updated
    // User proto for broadcasting, or nullopt if user not found.
    std::optional<voicecat::v1::User> clear_user_stream(uint32_t user_id, uint32_t stream_id);

    // Get all sessions in a channel except the one excluded (for SFU relay).
    std::vector<std::shared_ptr<ConnSession>> find_channel_sessions(
        uint32_t channel_id, uint64_t exclude_session_id = 0) const;

    // Return the channel_id of a user (0 if not found).
    uint32_t user_channel(uint32_t user_id) const;

    // Return a channel's authoritative AudioConfig (M3 per-channel Opus tuning), or nullopt
    // if the channel doesn't exist. There is no per-id Channel getter today otherwise —
    // channel_snapshot() copies every channel, which callers needing just one config should
    // avoid.
    std::optional<voicecat::v1::AudioConfig> channel_audio_config(uint32_t channel_id) const;

   private:
    void seed_default_channels();

    mutable std::shared_mutex mu_;

    std::shared_ptr<Database> db_;

    uint64_t next_session_id_{1};
    uint32_t next_user_id_{1};
    uint32_t next_channel_id_{3};  // 1 and 2 are reserved for Lobby, Music Room

    std::unordered_map<uint64_t, std::weak_ptr<ConnSession>> sessions_;
    std::unordered_map<uint32_t, UserEntry>                   users_;
    std::unordered_map<uint32_t, ChannelEntry>                channels_;
    std::unordered_map<uint64_t, voicecat::v1::Permissions>   session_permissions_;

    // M2: token → session_id (populated at auth, cleared on disconnect)
    struct TokenHash {
        size_t operator()(const std::array<uint8_t, 16>& t) const {
            // FNV-1a over 16 bytes
            size_t h = 14695981039346656037ULL;
            for (auto b : t) { h ^= b; h *= 1099511628211ULL; }
            return h;
        }
    };
    std::unordered_map<std::array<uint8_t, 16>, uint64_t, TokenHash> udp_tokens_;

    // M2: UDP endpoint → session_id (populated after UDP binding packet arrives)
    std::unordered_map<asio::ip::udp::endpoint, uint64_t, UdpEndpointHash> udp_endpoints_;

    // M2: ssrc → session_id (populated when StreamAnnounce is processed)
    std::unordered_map<uint32_t, uint64_t> ssrc_to_session_;
    std::atomic<uint32_t> next_ssrc_{1};
};

}  // namespace voicecat::server

#endif  // VOICECAT_SERVER_SESSION_REGISTRY_H
#endif  // VOICECAT_SERVER_SESSION_REGISTRY_H
