/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see .
 */
#include "ClubService.h"
#include "BattlenetRpcErrorCodes.h"
#include "ClubMembershipService.h"
#include "GameTime.h"
#include "Guild.h"
#include "Player.h"
#include "SocialMgr.h"
#include "api/client/v1/club_listener.pb.h"
namespace Battlenet::Services
{
ClubService::ClubService(WorldSession* session) : BaseService(session) { }
uint32 ClubService::HandleGetClubType(club::v1::client::GetClubTypeRequest const* request, club::v1::client::GetClubTypeResponse* response,
    std::function& /*continuation*/)
{
    // We only support guilds for now.
    if (request->type().name() == "guild")
    {
        response->set_allocated_type(CreateGuildClubType().release());
        return ERROR_OK;
    }
    return ERROR_NOT_IMPLEMENTED;
}
uint32 ClubService::HandleSubscribe(club::v1::client::SubscribeRequest const* /*request*/, NoData* /*response*/,
    std::function& /*continuation*/)
{
    Player const* player = _session->GetPlayer();
    if (!player)
        return ERROR_INTERNAL;
    Guild const* guild = player->GetGuild();
    if (!guild)
        return ERROR_CLUB_NO_CLUB;
    // Subscibe the client to it's own guild club.
    club::v1::client::SubscribeNotification subscribeNotification;
    Guild::Member const* guildMember = guild->GetMember(player->GetGUID());
    if (!guildMember)
        return ERROR_CLUB_NOT_MEMBER;
    Guild::Member const* guildLeader = guild->GetMember(guild->GetLeaderGUID());
    if (!guildLeader)
        return ERROR_CLUB_NO_SUCH_MEMBER;
    subscribeNotification.set_club_id(guild->GetId());
    subscribeNotification.set_allocated_agent_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release());
    club::v1::client::Club* guildClub = subscribeNotification.mutable_club();
    guildClub->set_id(guild->GetId());
    guildClub->set_allocated_type(CreateGuildClubType().release());
    guildClub->set_name(guild->GetName());
    // These are not related to normal guild functionality so we hardcode them for now.
    guildClub->set_privacy_level(club::v1::PrivacyLevel::PRIVACY_LEVEL_OPEN);
    guildClub->set_visibility_level(club::v1::VISIBILITY_LEVEL_PRIVATE);
    guildClub->set_member_count(guild->GetMembersCount());
    // Set the club leader, guild master in this case.
    club::v1::client::MemberDescription* guildLeaderDescription = guildClub->add_leader();
    guildLeaderDescription->mutable_id()->set_account_id(guildLeader->GetAccountId());
    guildLeaderDescription->mutable_id()->set_unique_id(guildLeader->GetGUID().GetCounter());
    club::v1::client::Member* subscriber = subscribeNotification.mutable_member();
    // The member sending the notification data.
    subscriber->set_allocated_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release());
    // Community/Club default roles have slightly different values.
    // Also this is required to set the current leader/guild master symbol in the interface.
    // 1 = Owner, 4 = Member. Once communities are fully implemented these will go into a new database table.
    if (guildMember->IsRank(GuildRankId::GuildMaster))
        subscriber->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner));
    else if (guild->HasAnyRankRight(guildMember->GetRankId(), GuildRankRights(GR_RIGHT_OFFCHATLISTEN | GR_RIGHT_OFFCHATSPEAK)))
        subscriber->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator));
    else
        subscriber->add_role(AsUnderlyingType(ClubRoleIdentifier::Member));
    subscriber->set_presence_level(club::v1::client::PRESENCE_LEVEL_RICH);
    subscriber->set_whisper_level(club::v1::client::WHISPER_LEVEL_OPEN);
    // Member is online and active.
    subscriber->set_active(true);
    WorldserverService(_session).OnSubscribe(&subscribeNotification, true, true);
    // Notify the client about the changed club state.
    club::v1::client::SubscriberStateChangedNotification subscriberStateChangedNotification;
    subscriberStateChangedNotification.set_club_id(guild->GetId());
    club::v1::client::SubscriberStateAssignment* assignment = subscriberStateChangedNotification.add_assignment();
    assignment->set_allocated_member_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release());
    // Member is online and active.
    assignment->set_active(true);
    WorldserverService(_session).OnSubscriberStateChanged(&subscriberStateChangedNotification, true, true);
    return ERROR_OK;
}
uint32 ClubService::HandleGetMembers(club::v1::client::GetMembersRequest const* /*request*/, club::v1::client::GetMembersResponse* response,
    std::function& /*continuation*/)
{
    Player const* player = _session->GetPlayer();
    if (!player)
        return ERROR_INTERNAL;
    Guild const* guild = player->GetGuild();
    if (!guild)
        return ERROR_CLUB_NO_CLUB;
    response->mutable_member()->Reserve(guild->GetMembersCount());
    for (auto const& [guid, member] : guild->GetMembers())
    {
        club::v1::client::Member* clubMember = response->add_member();
        clubMember->set_allocated_id(ClubMembershipService::CreateClubMemberId(guid).release());
        // Community/Club default roles have slightly different values.
        // When communities are implemented those are going to be database fields.
        if (member.IsRank(GuildRankId::GuildMaster))
            clubMember->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner));
        else if (guild->HasAnyRankRight(member.GetRankId(), GuildRankRights(GR_RIGHT_OFFCHATLISTEN | GR_RIGHT_OFFCHATSPEAK)))
            clubMember->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator));
        else
            clubMember->add_role(AsUnderlyingType(ClubRoleIdentifier::Member));
        clubMember->set_presence_level(club::v1::client::PresenceLevel::PRESENCE_LEVEL_RICH);
        clubMember->set_whisper_level(club::v1::client::WhisperLevel::WHISPER_LEVEL_OPEN);
        clubMember->set_note(member.GetPublicNote());
        clubMember->set_active(member.IsOnline());
    }
    return ERROR_OK;
}
uint32 ClubService::HandleGetStreams(club::v1::client::GetStreamsRequest const* /*request*/, club::v1::client::GetStreamsResponse* response,
    std::function& /*continuation*/)
{
    Player const* player = _session->GetPlayer();
    if (!player)
        return ERROR_INTERNAL;
    Guild const* guild = player->GetGuild();
    if (!guild)
        return ERROR_CLUB_NO_CLUB;
    // General guild channel.
    club::v1::client::Stream* generalGuildChannelStream = response->add_stream();
    generalGuildChannelStream->set_club_id(guild->GetId());
    generalGuildChannelStream->set_id(AsUnderlyingType(ClubStreamType::Guild));
    v2::Attribute* generalStreamAttribute = generalGuildChannelStream->add_attribute();
    generalStreamAttribute->set_name("global_strings_tag");
    generalStreamAttribute->mutable_value()->set_string_value("COMMUNITIES_GUILD_GENERAL_CHANNEL_NAME");
    generalGuildChannelStream->set_name("Guild");
    // All roles got access to this channel.
    // Club roles are currently guild role + 1.
    // With a complete club/community system those will be handled differently.
    generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner));
    generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Leader));
    generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator));
    generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Member));
    // No voice support.
    generalGuildChannelStream->set_voice_level(club::v1::client::StreamVoiceLevel::VOICE_LEVEL_DISABLED);
    // Officer guild channel.
    club::v1::client::Stream* officerGuildChannelStream = response->add_stream();
    officerGuildChannelStream->set_club_id(guild->GetId());
    officerGuildChannelStream->set_id(AsUnderlyingType(ClubStreamType::Officer));
    v2::Attribute* officerStreamAttribute = officerGuildChannelStream->add_attribute();
    officerStreamAttribute->set_name("global_strings_tag");
    officerStreamAttribute->mutable_value()->set_string_value("COMMUNITIES_GUILD_OFFICER_CHANNEL_NAME");
    officerGuildChannelStream->set_name("Officer");
    // All roles got access to this channel.
    // Club roles are currently guild role + 1.
    // With a complete club/community system those will be handled differently.
    officerGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner));
    officerGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Leader));
    officerGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator));
    // No voice support.
    officerGuildChannelStream->set_voice_level(club::v1::client::StreamVoiceLevel::VOICE_LEVEL_DISABLED);
    // Enable channel view
    club::v1::client::StreamView* generalView = response->add_view();
    generalView->set_club_id(guild->GetId());
    generalView->set_stream_id(AsUnderlyingType(ClubStreamType::Guild));
    club::v1::client::StreamView* officerView = response->add_view();
    officerView->set_club_id(guild->GetId());
    officerView->set_stream_id(AsUnderlyingType(ClubStreamType::Officer));
    return ERROR_OK;
}
uint32 ClubService::HandleSubscribeStream(club::v1::client::SubscribeStreamRequest const* request, NoData* /*response*/,
    std::function& /*continuation*/)
{
    Player const* player = _session->GetPlayer();
    if (!player)
        return ERROR_INTERNAL;
    Guild const* guild = player->GetGuild();
    if (!guild)
        return ERROR_CLUB_NO_CLUB;
    // Basic sanity check until full communities are implemented.
    // 1 - Guild, 2 - Officer chat stream.
    if (request->stream_id().empty() || (request->stream_id().Get(0) != AsUnderlyingType(ClubStreamType::Guild) && request->stream_id().Get(0) != AsUnderlyingType(ClubStreamType::Officer)))
        return ERROR_CLUB_STREAM_NO_STREAM;
    return ERROR_OK;
}
uint32 ClubService::HandleUnsubscribeStream(club::v1::client::UnsubscribeStreamRequest const* /*request*/, NoData* /*response*/,
    std::function& /*continuation*/)
{
    // We just have to signal the client that the unsubscribe request came through.
    return ERROR_OK;
}
uint32 ClubService::HandleSetStreamFocus(club::v1::client::SetStreamFocusRequest const* /*request*/, NoData* /*response*/,
    std::function& /*continuation*/)
{
    Player const* player = _session->GetPlayer();
    if (!player)
        return ERROR_INTERNAL;
    Guild const* guild = player->GetGuild();
    if (!guild)
        return ERROR_CLUB_NOT_MEMBER;
    return ERROR_OK;
}
uint32 ClubService::HandleAdvanceStreamViewTime(club::v1::client::AdvanceStreamViewTimeRequest const* /*request*/, NoData* /*response*/,
    std::function& /*continuation*/)
{
    Player const* player = _session->GetPlayer();
    if (!player)
        return ERROR_INTERNAL;
    Guild const* guild = player->GetGuild();
    if (!guild)
        return ERROR_CLUB_NOT_MEMBER;
    return ERROR_OK;
}
uint32 ClubService::HandleCreateMessage(club::v1::client::CreateMessageRequest const* request, club::v1::client::CreateMessageResponse* response,
    std::function& continuation)
{
    // Basic sanity check until full communities are implemented.
    // 1 - Guild, 2 - Officer chat stream.
    if (request->stream_id() != AsUnderlyingType(ClubStreamType::Guild) && request->stream_id() != AsUnderlyingType(ClubStreamType::Officer))
        return ERROR_CLUB_STREAM_NO_STREAM;
    // Just some sanity checks. We do not care about the requested stream for now since we only have two.
    Player const* player = _session->GetPlayer();
    if (!player)
        return ERROR_INTERNAL;
    Guild const* guild = player->GetGuild();
    if (!guild)
        return ERROR_CLUB_NO_CLUB;
    GuildRankRights requiredRights = { };
    ChatMessageResult result = { };
    switch (ClubStreamType(request->stream_id()))
    {
        case ClubStreamType::Guild:
            requiredRights = GR_RIGHT_GCHATLISTEN;
            result = _session->HandleChatMessage(CHAT_MSG_GUILD, LANG_UNIVERSAL, request->options().content());
            break;
        case ClubStreamType::Officer:
            requiredRights = GR_RIGHT_OFFCHATLISTEN;
            result = _session->HandleChatMessage(CHAT_MSG_OFFICER, LANG_UNIVERSAL, request->options().content());
            break;
        default:
            return ERROR_CLUB_STREAM_NO_STREAM;
    }
    if (result == ChatMessageResult::Ok)
    {
        std::chrono::microseconds messageTime = std::chrono::duration_cast(GameTime::GetSystemTime().time_since_epoch());
        FillStreamMessage(response->mutable_message(), request->options().content(), messageTime, player->GetGUID());
        club::v1::client::StreamMessageAddedNotification messageAddedNotification;
        messageAddedNotification.set_allocated_agent_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release());
        messageAddedNotification.set_club_id(guild->GetId());
        messageAddedNotification.set_stream_id(request->stream_id());
        FillStreamMessage(messageAddedNotification.mutable_message(), request->options().content(), messageTime, player->GetGUID());
        guild->BroadcastWorker([&](Player const* receiver)
        {
            Guild::Member const* receiverMember = guild->GetMember(receiver->GetGUID());
            if (!guild->HasAnyRankRight(receiverMember->GetRankId(), requiredRights))
                return;
            if (receiver->GetSocial()->HasIgnore(player->GetGUID(), _session->GetAccountGUID()))
                return;
            WorldserverService(receiver->GetSession()).OnStreamMessageAdded(&messageAddedNotification, true, true);
        }, player);
        return ERROR_OK;
    }
    // If the message is empty there should never be a response to message request.
    continuation = nullptr;
    return ERROR_CLUB_STREAM_NO_SUCH_MESSAGE;
}
std::unique_ptr ClubService::CreateGuildClubType()
{
    std::unique_ptr type = std::make_unique();
    type->set_program(5730135);
    type->set_name("guild");
    return type;
}
void ClubService::FillStreamMessage(club::v1::client::StreamMessage* message, std::string_view msg, std::chrono::microseconds messageTime, ObjectGuid author)
{
    message->mutable_id()->set_epoch(messageTime.count());
    message->mutable_id()->set_position(0);
    message->mutable_author()->set_allocated_id(ClubMembershipService::CreateClubMemberId(author).release());
    club::v1::client::ContentChain* contentChain = message->add_content_chain();
    contentChain->set_content(msg.data(), msg.size());
    contentChain->set_edit_time(messageTime.count());
}
}