/*
 * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>,
 * released under GNU AGPL v3 license:
 * https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
 */

#include "PlayerGuideMgr.h"
#include "PlayerGuideTransport.h"

#include "../IndividualProgression.h"

#include "Chat.h"
#include "Config.h"
#include "Creature.h"
#include "Log.h"
#include "Player.h"
#include "QuestDef.h"
#include "ScriptMgr.h"
#include "SharedDefines.h"

#include <cstdlib>
#include <string>

namespace MoonWell::PlayerGuide
{
    namespace
    {
        void Push(Player* player)
        {
            if (!sPlayerGuideMgr->IsEnabled())
                return;
            if (!player || !player->GetSession() || player->IsBeingTeleported())
                return;

            Payload p = sPlayerGuideMgr->BuildFor(player);
            std::string json = sPlayerGuideMgr->SerializeJson(p);

            if (sPlayerGuideMgr->LogPayloads())
                LOG_DEBUG("module", "[PlayerGuide] push {} bytes -> {}",
                    json.size(), player->GetName());

            Transport::SendSetData(player, json);
        }

        // Splits "MWPG\t<cmd>\t<arg>" -> (cmd, arg). Returns false if
        // the message does not match the expected addon prefix.
        bool ParseClientMessage(std::string const& in, std::string& cmd,
                                std::string& arg)
        {
            // Expected prefix: "MWPG\t".
            if (in.size() < 6)
                return false;
            if (in.compare(0, 4, ADDON_PREFIX) != 0)
                return false;
            if (in[4] != '\t')
                return false;

            std::size_t end = in.find('\t', 5);
            if (end == std::string::npos)
            {
                cmd.assign(in, 5, in.size() - 5);
                arg.clear();
                return true;
            }
            cmd.assign(in, 5, end - 5);
            arg.assign(in, end + 1, in.size() - (end + 1));
            return true;
        }
    }

    class PlayerGuide_PlayerScript : public PlayerScript
    {
    public:
        PlayerGuide_PlayerScript()
            : PlayerScript("PlayerGuide_PlayerScript",
            {
                PLAYERHOOK_ON_LOGIN,
                PLAYERHOOK_ON_LEVEL_CHANGED,
                PLAYERHOOK_ON_PLAYER_COMPLETE_QUEST,
                PLAYERHOOK_ON_CREATURE_KILL,
                PLAYERHOOK_ON_BEFORE_SEND_CHAT_MESSAGE
            }) { }

        void OnPlayerLogin(Player* player) override
        {
            Push(player);
        }

        void OnPlayerLevelChanged(Player* player, uint8 /*oldLevel*/) override
        {
            Push(player);
        }

        void OnPlayerCompleteQuest(Player* player,
                                   Quest const* /*quest*/) override
        {
            Push(player);
        }

        void OnPlayerCreatureKill(Player* killer, Creature* killed) override
        {
            if (!killer || !killed)
                return;
            if (killed->GetCreatureTemplate()->rank <= CREATURE_ELITE_NORMAL)
                return;
            Push(killer);
        }

        void OnPlayerBeforeSendChatMessage(Player* player, uint32& type,
                                           uint32& lang, std::string& msg) override
        {
            if (!sPlayerGuideMgr->IsEnabled())
                return;
            if (type != CHAT_MSG_WHISPER || lang != LANG_ADDON)
                return;

            if (sPlayerGuideMgr->LogPayloads())
                LOG_DEBUG("module",
                    "[PlayerGuide] raw addon <- {} ({} bytes): '{}'",
                    player->GetName(), msg.size(), msg);

            std::string cmd, arg;
            if (!ParseClientMessage(msg, cmd, arg))
                return;

            LOG_INFO("module",
                "[PlayerGuide] <- {} cmd='{}' arg='{}' ({} bytes)",
                player->GetName(), cmd, arg, msg.size());

            if (cmd == ClientCmd::REQUEST_REFRESH)
            {
                Push(player);
            }
            else if (cmd == ClientCmd::REQUEST_TRACK_OBJECTIVE)
            {
                // Tracking is purely client-side state for now; we just
                // re-send the payload so the client can update its
                // tracked id atomically.
                Push(player);
            }
            else if (cmd == ClientCmd::REQUEST_OPEN_TARGET)
            {
                // Reserved for future: server-driven open-target action
                // (e.g. queue LFG). For MVP a refresh is enough.
                Push(player);
            }
            else
            {
                LOG_WARN("module",
                    "[PlayerGuide] unknown cmd='{}' from {}",
                    cmd, player->GetName());
                return;
            }

            // Swallow the message so it does not get relayed as a real
            // whisper. Clearing the body is enough — ChatHandler
            // returns early on empty addon messages.
            msg.clear();
        }
    };

    class PlayerGuide_WorldScript : public WorldScript
    {
    public:
        PlayerGuide_WorldScript()
            : WorldScript("PlayerGuide_WorldScript",
            {
                WORLDHOOK_ON_AFTER_CONFIG_LOAD,
                WORLDHOOK_ON_LOAD_CUSTOM_DATABASE_TABLE
            }) { }

        void OnAfterConfigLoad(bool /*reload*/) override
        {
            sPlayerGuideMgr->LoadConfig();
            LOG_INFO("server.loading",
                ">> PlayerGuide module: {}.",
                sPlayerGuideMgr->IsEnabled() ? "enabled" : "disabled");
        }

        void OnLoadCustomDatabaseTable() override
        {
            // Re-read custom_ej_instance after the EJ loader has had
            // a chance to populate it. Order between modules on this
            // hook is undefined, but we are reading the table itself
            // (not EJ's in-memory cache) so we are fine as long as
            // the SQL has been applied.
            sPlayerGuideMgr->ReloadEncounterJournalCache();
        }
    };
}

void AddSC_mod_individual_progression_player_guide()
{
    new MoonWell::PlayerGuide::PlayerGuide_PlayerScript();
    new MoonWell::PlayerGuide::PlayerGuide_WorldScript();
}
