/*
 * server/conn_session.h — Per-client connection state machine.
 *
 * State: WaitingHello → WaitingAuth → Authenticated → Disconnecting
 *
 * Design: ConnSession is a pure state machine. It receives frames via on_frame()
 * (called from TcpServerConn's strand) and sends via a send_fn set after construction.
 * The server creates the TcpServerConn first (with callbacks referencing the session),
 * then calls set_tcp() to give the session its send capability.
 */
#ifndef VOICECAT_SERVER_CONN_SESSION_H
#define VOICECAT_SERVER_CONN_SESSION_H

#ifdef VOICECAT_HAS_NET

#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>

#define ASIO_STANDALONE 1
#include <asio.hpp>

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

namespace voicecat { class WorkerPool; }

namespace voicecat::server {

class Database;
class SessionRegistry;

class ConnSession : public std::enable_shared_from_this<ConnSession> {
   public:
    enum class State { WaitingHello, WaitingAuth, Authenticated, Disconnecting };

    using SendFn  = std::function<void(std::vector<uint8_t>)>;
    using CloseFn = std::function<void()>;

    ConnSession(std::shared_ptr<Database>              db,
                std::shared_ptr<SessionRegistry>       registry,
                std::shared_ptr<voicecat::WorkerPool>  workers,
                const std::array<uint8_t, 32>&  server_fp,
                bool                             allow_guests,
                uint16_t                         udp_media_port = 0);

    void set_io(SendFn send_fn, CloseFn close_fn);
    void set_session_id(uint64_t id) { session_id_ = id; }

    void begin();
    void on_frame(std::vector<uint8_t> frame);
    void on_disconnect();
    void send_envelope(const voicecat::v1::Envelope& env);
    void close();

    // Called by SessionRegistry for kick/ban. Public so the registry can forcibly
    // close a session without making it a friend class.
    void send_disconnect_and_close(uint32_t code, const std::string& reason);

    // ── M2: media key injection (called from on_tls_ready) ───────────────────
    void set_media_crypto(std::unique_ptr<voicecat::crypto::SodiumMediaCrypto> send,
                          std::unique_ptr<voicecat::crypto::SodiumMediaCrypto> recv);

    // ── M2: UDP endpoint (set by MediaRelay on UdpBinding) ────────────────────
    void set_udp_endpoint(asio::ip::udp::endpoint ep);
    asio::ip::udp::endpoint udp_endpoint() const;
    bool has_udp_endpoint() const { return has_udp_ep_.load(); }

    // ── M2: media crypto access (for SFU relay) ──────────────────────────────
    voicecat::crypto::SodiumMediaCrypto* send_crypto();
    voicecat::crypto::SodiumMediaCrypto* recv_crypto();

    // ── M2: UDP token (for binding) ───────────────────────────────────────────
    const std::array<uint8_t, 16>& udp_token() const { return udp_token_; }

    // ── Accessors ──────────────────────────────────────────────────────────────
    State    state()      const { return state_.load(); }
    uint64_t session_id() const { return session_id_; }
    uint32_t user_id()    const { return user_id_; }

    // Keepalive: bump last_seen to "now" on any inbound activity (TCP frame or UDP voice
    // frame). The server's reaper sweeps sessions whose last_seen is older than the
    // timeout (docs/protocol.md §7). Stored as steady_clock ms since epoch in an atomic so
    // the reaper thread can read it lock-free.
    void touch_last_seen() {
        last_seen_ms_.store(std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::steady_clock::now().time_since_epoch()).count(),
            std::memory_order_release);
    }
    int64_t last_seen_ms() const { return last_seen_ms_.load(std::memory_order_acquire); }

   private:
    void handle_client_hello(uint64_t req_id, const voicecat::v1::ClientHello& msg);
    void handle_auth_request(uint64_t req_id, const voicecat::v1::AuthRequest& msg);
    void handle_join_channel(uint64_t req_id, const voicecat::v1::JoinChannelRequest& msg);
    void handle_text_message(const voicecat::v1::TextMessage& msg);
    void handle_ping(const voicecat::v1::Ping& msg);
    void handle_client_disconnect(const voicecat::v1::Disconnect& msg);
    void handle_udp_binding(uint64_t req_id, const voicecat::v1::UdpBinding& msg);
    void handle_stream_announce(uint64_t req_id, const voicecat::v1::StreamAnnounce& msg);
    void handle_stream_stop(const voicecat::v1::StreamStop& msg);
    void handle_leave_channel();

    // M5 handlers
    void handle_kick_request(uint64_t req_id, const voicecat::v1::KickRequest& msg);
    void handle_ban_request(uint64_t req_id, const voicecat::v1::BanRequest& msg);
    void handle_set_permission(uint64_t req_id, const voicecat::v1::SetPermissionRequest& msg);
    void handle_server_mute_request(uint64_t req_id, const voicecat::v1::ServerMuteRequest& msg);
    void handle_move_user(uint64_t req_id, const voicecat::v1::MoveUserRequest& msg);
    void handle_create_channel(uint64_t req_id, const voicecat::v1::CreateChannelRequest& msg);
    void handle_edit_channel(uint64_t req_id, const voicecat::v1::EditChannelRequest& msg);
    void handle_delete_channel(uint64_t req_id, const voicecat::v1::DeleteChannelRequest& msg);
    void handle_create_account(uint64_t req_id, const voicecat::v1::CreateAccountRequest& msg);
    void handle_reset_password(uint64_t req_id, const voicecat::v1::ResetPasswordRequest& msg);
    void handle_delete_account(uint64_t req_id, const voicecat::v1::DeleteAccountRequest& msg);
    void handle_list_accounts(uint64_t req_id, const voicecat::v1::ListAccountsRequest& msg);

    void finish_guest_auth(const voicecat::v1::GuestAuth& guest, uint64_t req_id);
    void finish_password_auth(const std::string& username, const std::string& password,
                              uint64_t req_id);
    void send_auth_result_ok(uint64_t req_id, const voicecat::v1::User& user,
                              const voicecat::v1::Permissions* perms = nullptr);
    void send_state_snapshot();
    void broadcast_user_joined(const voicecat::v1::User& user);
    void send_generic_result(uint64_t req_id, bool ok, uint32_t code,
                              const std::string& message);

    // Permission helpers.
    bool has_permission(bool (voicecat::v1::Permissions::* getter)() const) const;
    bool is_admin() const { return has_permission(&voicecat::v1::Permissions::is_admin); }
    void set_permissions(const voicecat::v1::Permissions& perms);

    std::shared_ptr<Database>             db_;
    std::shared_ptr<SessionRegistry>      registry_;
    std::shared_ptr<voicecat::WorkerPool> workers_;
    std::array<uint8_t, 32>              server_fp_;
    bool                                  allow_guests_;
    uint16_t                              udp_media_port_;

    SendFn             send_fn_;
    CloseFn            close_fn_;
    std::atomic<State> state_{State::WaitingHello};
    uint64_t           session_id_{0};
    std::atomic<uint32_t> user_id_{0};
    std::atomic<bool>  closed_{false};

    // Keepalive: steady_clock ms since epoch of the last inbound activity. Bumped by
    // touch_last_seen() on every on_frame() and (via media_relay) on every UDP voice frame.
    // The reaper (server.cpp) drops sessions whose last_seen is older than 45s.
    std::atomic<int64_t> last_seen_ms_{0};

    // M2 UDP / media
    std::array<uint8_t, 16>  udp_token_{};
    mutable std::mutex        udp_ep_mu_;
    asio::ip::udp::endpoint   udp_ep_;
    std::atomic<bool>         has_udp_ep_{false};

    mutable std::mutex        crypto_mu_;
    std::unique_ptr<voicecat::crypto::SodiumMediaCrypto> send_crypto_;
    std::unique_ptr<voicecat::crypto::SodiumMediaCrypto> recv_crypto_;

    // M2/M3: locally-announced streams. The server assigns the stream_id (unique per
    // session), so a per-session counter + the set of currently-active ids is enough to
    // support multiple concurrent streams (MIC + SCREEN_AUDIO + AUX_DEVICE) per user.
    uint32_t              next_stream_id_{1};
    std::vector<uint32_t> announced_stream_ids_;

    // M5: permissions granted at auth time (server-side authority).
    voicecat::v1::Permissions permissions_;
};

}  // namespace voicecat::server

#endif  // VOICECAT_HAS_NET
#endif  // VOICECAT_SERVER_CONN_SESSION_H
