#include "Context.h"
#include "Base/HttpSession.h"
#include "Base/Messages.h"
#include "Core/Logger.h"
#include "Core/MessageManager.h"
#include "Http/Utility.h"
#include "WXBizMsgCrypt.h"
#include <boost/asio/defer.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/version.hpp>
#include <boost/format.hpp>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/url/parse.hpp>

namespace WeChat {
namespace Corporation {
Context::Context(boost::asio::io_context &ioContext) : m_ioContext(ioContext), m_timer(ioContext) {
    using namespace Core;
    auto manager = Singleton<MessageManager>::instance();
    if (manager) {
        manager->subscribe<Older::NotifyServerChan>(
            [this](const boost::beast::http::request<boost::beast::http::string_body> &request) { notify(request); });
    }
    registerMoneyNote();
}

void Context::sendMessage(MessageType type, const std::string &message) {
    boost::format target("/cgi-bin/message/send?access_token=%1%");
    target % m_accessToken;

    boost::json::object msg;
    msg["content"] = message;

    boost::json::object request;
    request["touser"] = "@all";
    request["agentid"] = agentid;
    if (type == MessageType::Markdown) {
        request["msgtype"] = "markdown";
        request["markdown"] = std::move(msg);
    } else {
        request["msgtype"] = "text";
        request["text"] = std::move(msg);
    }
    auto body = boost::json::serialize(request);

    boost::beast::error_code error;
    auto response = Https::post(m_ioContext, host, port, target.str(), body, error);
    if (error) {
        LOG(error) << error.message();
        return;
    }
    LOG(info) << response;
}

void Context::start() {
    boost::asio::defer(m_ioContext, [ptr{weak_from_this()}]() {
        if (ptr.expired()) {
            LOG(error) << "Context instance was expired";
            return;
        }
        auto self = ptr.lock();
        self->updateAccessToken();
    });
}

void Context::notify(const RequestType &request) {
    boost::system::error_code error;
    auto json = boost::json::parse(request.body(), error);
    if (error) {
        LOG(error) << "parse: [" << request.body() << "] failed, reason: " << error.message();
        return;
    }
    // LOG(debug) << "parse: [" << request.body() << "] succeed.";
    auto &req = json.as_object();
    MessageType type = MessageType::Text;
    if (req.contains("type")) {
        if (req.at("type").as_string() == "markdown") {
            type = MessageType::Markdown;
        }
    }
    if (req.contains("msg")) {
        std::string msg(req.at("msg").as_string());
        sendMessage(type, std::move(msg));
    }
}

void Context::updateAccessToken() {
    boost::beast::error_code error;

    boost::format target("/cgi-bin/gettoken?corpid=%1%&corpsecret=%2%");
    target % corpid % corpsecret;

    auto response = Https::get(m_ioContext, host, port, target.str(), error);
    if (error) {
        LOG(error) << error.message();
        return;
    }
    if (response.empty()) {
        LOG(warning) << "response is empty.";
        return;
    }

    auto json = boost::json::parse(response);
    auto &accessTokenObject = json.as_object();
    int errcode = accessTokenObject.count("errcode") > 0 ? accessTokenObject.at("errcode").as_int64() : -1;
    if (errcode != 0) {
        LOG(error) << "get access_token failed,code: " << errcode << ", message: " << accessTokenObject.at("errmsg").as_string();
        return;
    }
    m_accessToken = accessTokenObject.at("access_token").as_string();
    auto expires_in = accessTokenObject.at("expires_in").as_int64();
    // LOG(info) << "access_token: " << m_accessToken;
    LOG(info) << "re-access_token after " << expires_in << " s.";
    m_timer.expires_after(std::chrono::seconds(expires_in));
    m_timer.async_wait([this](const boost::system::error_code &error) {
        if (error) {
            LOG(error) << error.message();
            return;
        }
        updateAccessToken();
    });

    static bool started = true;
    if (started) {
        sendMessage(MessageType::Text, "您好,艾玛已上线......");
        started = false;
    }
}

void Context::registerMoneyNote() {
    // 接口说明: https://developer.work.weixin.qq.com/document/10514
    // https://amass.fun/api/v1/wx/moneynote
    // /api/v1/wx/moneynote?msg_signature=1096371bf526609f8aaacc247b3bebf2fbe15969&timestamp=1749021023&nonce=1748464240&echostr=1jccl4g1jk%2FMqIHQ445D9yOikKpcKkYOCMCLxqPSpKBtkMuKAuLGe8c6dbsTN5LZmRy0a45mxGvpqntvb3Vvxg%3D%3D
    using namespace Core;
    using namespace Older;
    using namespace boost::urls;
    using namespace boost::beast;
    auto manager = Singleton<MessageManager>::instance();
    // clang-format off
    manager->publish<RegisterUrlHandler>("/api/v1/wx/moneynote", [this](HttpSession &session, const HttpRequest &request, const matches &matches) {
        auto path = boost::urls::parse_origin_form(request.target());
        auto params = path->params();
        auto msg_signature = params.find("msg_signature");
        auto timestamp = params.find("timestamp");
        auto nonce = params.find("nonce");

        constexpr auto token = "BqnhzhsRqIJXO5k";
        constexpr auto encodingAESKey = "XMZ74tSRRe9MtEsEc9ihBTmvXxOisDrrFsukmcoPJR4";
        constexpr auto corpid = "ww1a786851749bdadc";
        std::string reply;
        Tencent::WXBizMsgCrypt wxcpt(token, encodingAESKey, corpid);
        if (request.method() == http::verb::get) {
            auto echostr = params.find("echostr");
            wxcpt.VerifyURL((*msg_signature).value, (*timestamp).value, (*nonce).value, (*echostr).value, reply);
        } else {
            std::string message;
            wxcpt.DecryptMsg((*msg_signature).value, (*timestamp).value, (*nonce).value, request.body(), message);
            boost::property_tree::ptree ptree;
            std::istringstream iss(message);
            boost::property_tree::read_xml(iss, ptree);
            if (ptree.count("xml.Content") > 0) {
                std::string content = ptree.get<std::string>("xml.Content");
                LOG(info) << "content: " << content;
            }
            ptree.put("xml.Content","i hate you.");
            std::ostringstream oss;
            boost::property_tree::write_xml(oss,ptree);
            wxcpt.EncryptMsg(oss.str(), (*timestamp).value, (*nonce).value, reply);
        }
      
        LOG(info) << "reply: " << reply;
        http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
        s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        s.set(http::field::content_type, "application/json;charset=UTF-8");
        s.keep_alive(request.keep_alive());
        s.body() = reply;
        s.prepare_payload();
        session.reply(std::move(s));
    });
    // clang-format on
}

} // namespace Corporation
} // namespace WeChat