#include "session/session.h"

#include <algorithm>

namespace voicecat::session {

const Channel* SessionModel::find_channel(uint32_t id) const {
    for (auto& ch : channels_) if (ch.id == id) return &ch;
    return nullptr;
}

const User* SessionModel::find_user(uint32_t id) const {
    for (auto& u : users_) if (u.id == id) return &u;
    return nullptr;
}

std::pair<const User*, const Stream*> SessionModel::find_user_by_ssrc(uint32_t ssrc) const {
    for (auto& u : users_) {
        for (auto& s : u.streams) {
            if (s.ssrc == ssrc) return {&u, &s};
        }
    }
    return {nullptr, nullptr};
}

#ifdef VOICECAT_HAS_NET

namespace {
std::vector<Stream> copy_streams(
    const google::protobuf::RepeatedPtrField<voicecat::v1::StreamInfo>& src) {
    std::vector<Stream> out;
    out.reserve(src.size());
    for (const auto& pb : src) {
        Stream s;
        s.stream_id   = pb.stream_id();
        s.ssrc        = pb.ssrc();
        s.kind        = static_cast<int>(pb.kind());
        s.label       = pb.label();
        s.sample_rate = pb.audio().sample_rate() ? pb.audio().sample_rate() : 48000;
        s.frame_ms    = pb.audio().frame_ms() ? pb.audio().frame_ms() : 20;
        s.mode                 = static_cast<uint32_t>(pb.audio().mode());
        s.bitrate_bps          = pb.audio().bitrate_bps();
        s.application          = static_cast<uint32_t>(pb.audio().application());
        s.fec                  = pb.audio().fec();
        s.expected_packet_loss = pb.audio().expected_packet_loss();
        s.dtx                  = pb.audio().dtx();
        s.complexity           = pb.audio().complexity();
        s.dred                 = pb.audio().dred();
        out.push_back(std::move(s));
    }
    return out;
}
}  // namespace

void SessionModel::apply_snapshot(const voicecat::v1::ServerStateSnapshot& snap) {
    channels_.clear();
    for (const auto& pb : snap.channels()) {
        Channel ch;
        ch.id                 = pb.id();
        ch.parent_id          = pb.parent_id();
        ch.name               = pb.name();
        ch.topic              = pb.topic();
        ch.password_protected = pb.password_protected();
        ch.max_users          = pb.max_users();
        channels_.push_back(std::move(ch));
    }

    users_.clear();
    for (const auto& pb : snap.users()) {
        User u;
        u.id              = pb.id();
        u.nickname        = pb.nickname();
        u.is_guest        = pb.is_guest();
        u.channel_id      = pb.channel_id();
        u.self_mic_muted  = pb.self_mic_muted();
        u.self_deafened   = pb.self_deafened();
        u.server_muted    = pb.server_muted();
        u.server_deafened = pb.server_deafened();
        u.streams         = copy_streams(pb.streams());
        users_.push_back(std::move(u));
    }
}

void SessionModel::apply_user_event(const voicecat::v1::UserEvent& ev) {
    using Kind = voicecat::v1::UserEvent;

    if (ev.kind() == Kind::JOINED || ev.kind() == Kind::UPDATED) {
        const auto& pb = ev.user();
        User u;
        u.id              = pb.id();
        u.nickname        = pb.nickname();
        u.is_guest        = pb.is_guest();
        u.channel_id      = pb.channel_id();
        u.self_mic_muted  = pb.self_mic_muted();
        u.self_deafened   = pb.self_deafened();
        u.server_muted    = pb.server_muted();
        u.server_deafened = pb.server_deafened();
        u.streams         = copy_streams(pb.streams());

        auto it = std::find_if(users_.begin(), users_.end(),
                               [&](const User& x) { return x.id == u.id; });
        if (it != users_.end()) *it = std::move(u);
        else                    users_.push_back(std::move(u));

    } else if (ev.kind() == Kind::LEFT) {
        uint32_t uid = ev.user().id();
        users_.erase(std::remove_if(users_.begin(), users_.end(),
                                    [uid](const User& x) { return x.id == uid; }),
                     users_.end());
    }
}

void SessionModel::apply_channel_event(const voicecat::v1::ChannelEvent& ev) {
    using Kind = voicecat::v1::ChannelEvent;

    if (ev.kind() == Kind::CREATED || ev.kind() == Kind::UPDATED) {
        const auto& pb = ev.channel();
        Channel ch;
        ch.id                 = pb.id();
        ch.parent_id          = pb.parent_id();
        ch.name               = pb.name();
        ch.topic              = pb.topic();
        ch.password_protected = pb.password_protected();
        ch.max_users          = pb.max_users();

        auto it = std::find_if(channels_.begin(), channels_.end(),
                               [&](const Channel& x) { return x.id == ch.id; });
        if (it != channels_.end()) *it = std::move(ch);
        else                       channels_.push_back(std::move(ch));

    } else if (ev.kind() == Kind::DELETED) {
        // deleted_id, not channel().id() — the proto leaves `channel` unset for deletes
        // (docs/protocol.md, core/proto/voicecat.proto's ChannelEvent). Pre-existing bug, dead
        // code until something actually emits ChannelEvent (no channel CRUD exists yet — M5+),
        // fixed here while touching this function for the M4 field-population fix.
        uint32_t cid = ev.deleted_id();
        channels_.erase(std::remove_if(channels_.begin(), channels_.end(),
                                       [cid](const Channel& x) { return x.id == cid; }),
                        channels_.end());
    }
}

#endif  // VOICECAT_HAS_NET

}  // namespace voicecat::session
