#include "ServiceLogic.h"
#include "Base/Database.h"
#include "Base/HttpSession.h"
#include "Core/Singleton.h"
#include "SessionStore.h"
#include "Settings.h"
#include <sstream>

namespace ServiceLogic {
using namespace boost::beast;

std::string extractToken(const std::string &cookieHeader, const std::string &tokenName = "access_token") {
    // 格式示例："access_token=abc123; Path=/; Expires=Wed, 21 Oct 2023 07:28:00 GMT"
    size_t startPos = cookieHeader.find(tokenName + "=");
    if (startPos == std::string::npos) {
        return "";
    }
    startPos += tokenName.size() + 1; // 跳过 "token_name="
    size_t endPos = cookieHeader.find(';', startPos);
    if (endPos == std::string::npos) {
        endPos = cookieHeader.size();
    }
    std::string token = cookieHeader.substr(startPos, endPos - startPos);

    // 移除可能的引号和空格
    token.erase(std::remove(token.begin(), token.end(), '"'), token.end());
    token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
    return token;
}

boost::beast::http::response<boost::beast::http::string_body>
serverError(const boost::beast::http::request<boost::beast::http::string_body> &request, std::string_view errorMessage) {
    using namespace boost::beast;
    http::response<http::string_body> res{http::status::internal_server_error, request.version()};
    res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
    res.set(http::field::content_type, "text/html");
    res.keep_alive(request.keep_alive());
    std::ostringstream oss;
    oss << "An error occurred: '" << errorMessage << "'";
    res.body() = oss.str();
    res.prepare_payload();
    return res;
}

http::response<http::string_body> badRequest(const http::request<http::string_body> &request, std::string_view why) {
    http::response<http::string_body> res{http::status::bad_request, request.version()};
    res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
    res.set(http::field::content_type, "text/html");
    res.keep_alive(request.keep_alive());
    res.body() = std::string(why);
    res.prepare_payload();
    return res;
}

void staticFilesDeploy() {
    using namespace Core;
    using namespace boost::urls;
    auto application = Singleton<Older::Application>::instance();
    // clang-format off
    application->insertUrl("/{path*}", [](Older::HttpSession &session, const Older::HttpRequest &request, const matches &matches) {
        using namespace boost::beast;
        url_view view(request.target());
        auto target = view.path();
        LOG(info) << target;
        if (target.find("..") != boost::beast::string_view::npos) {
            session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
            return;
        }
        auto settings = Singleton<Older::Settings>::instance();
        std::string path = ResponseUtility::pathCat(settings->documentRoot(), target);
        if (target.back() == '/') path.append("index.html");
        if (std::filesystem::is_directory(path)) path.append("/index.html");
        boost::beast::error_code ec;
        http::file_body::value_type body;
        body.open(path.c_str(), boost::beast::file_mode::scan, ec);
        if (ec == boost::beast::errc::no_such_file_or_directory) {
            std::ostringstream oss;
            oss << "The resource '" << target << "' was not found.";
            LOG(error) << oss.str();
            session.errorReply(request, http::status::not_found, oss.str());
            return;
        } else if (ec) {
            session.reply(ServiceLogic::serverError(request, ec.message()));
            return;
        }
        auto const size = body.size();
        http::response<http::file_body> res{std::piecewise_construct, std::make_tuple(std::move(body)),
                                            std::make_tuple(http::status::ok, request.version())};
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, ResponseUtility::mimeType(path));
        res.content_length(size);
        res.keep_alive(request.keep_alive());
        session.reply(std::move(res));
    });
    // clang-format on
}

void visitAnalysis() {
    using namespace Core;
    // clang-format off
    static std::vector<std::string> urlFilter = {
        "/",          
        "/search",       
        "/login",
        "/redirect",  
        "/MessageBoard", 
        "/我的笔记",
        "/我的笔记/", 
        "/我的博客",     
        "/我的博客/2020/11/22/如何给岛国小电影找字幕",
    };
    // clang-format on

    auto application = Singleton<Older::Application>::instance();
    application->insertUrl("/api/v1/visit_analysis", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                        const boost::urls::matches &matches) {
        using namespace std::chrono;
        using namespace boost::beast;
        using namespace Core;
        auto rootJson = boost::json::parse(request.body());
        auto &root = rootJson.as_object();
        std::string url;
        if (root.contains("url")) {
            url = root["url"].as_string();
            if (!url.empty() && (url.back() == '/')) {
                url.pop_back();
            }
        }

        auto database = Singleton<Older::Database>::instance();
        auto settings = Singleton<Older::Settings>::instance();
        auto documentRoot = settings->documentRoot();
        if (!url.empty() && (documentRoot.empty() || std::filesystem::exists(documentRoot + url))) {
            if (url.find("/我的博客/page") != 0) { // Docusaurus 这个路径过滤掉
                if (root.contains("visitor_uuid") && root.contains("user_agent")) {
                    auto timestamp = duration_cast<seconds>(system_clock::now().time_since_epoch());
                    auto visitorUuid = std::string(root["visitor_uuid"].as_string());
                    auto userAgent = std::string(root["user_agent"].as_string());
                    database->upsertVisitRecord(url, visitorUuid, userAgent, timestamp.count());
                }
            }
        }

        auto urlStats = database->visitorStats(url);
        auto siteStats = database->siteStats();

        boost::json::object reply;
        reply["page_view_count"] = urlStats.totalViews;
        reply["unique_visitor_count"] = urlStats.visitorCount;
        reply["site_page_view_count"] = siteStats.totalViews;
        reply["site_unique_visitor_count"] = siteStats.totalVisitors;

        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() = boost::json::serialize(reply);
        s.prepare_payload();
        session.reply(std::move(s));
    });

    application->insertUrl("/api/v1/most_viewed_urls", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                          const boost::urls::matches &matches) {
        using namespace boost::beast;
        int size = 5;
        std::error_code error;
        if (!request.body().empty()) {
            auto rootJson = boost::json::parse(request.body(), error);
            if (error) {
                LOG(info) << "<" << request.body() << "> parse json error: " << error.message();
            } else {
                auto &root = rootJson.as_object();
                if (root.contains("size")) {
                    size = root.at("size").as_int64();
                }
            }
        }

        auto database = Singleton<Older::Database>::instance();
        auto stats = database->mostViewedUrls(size + urlFilter.size());
        boost::json::array reply;
        int index = 0;
        for (auto stat : stats) {
            if (std::find(urlFilter.cbegin(), urlFilter.cend(), stat.url) != urlFilter.cend()) continue;
            boost::json::object object;
            object["url"] = stat.url;
            object["count"] = stat.totalViews;
            reply.push_back(std::move(object));
            index++;
            if (index >= size) break;
        }
        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() = boost::json::serialize(reply);
        s.prepare_payload();
        session.reply(std::move(s));
    });

    application->insertUrl("/api/v1/latest_viewed_urls", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                            const boost::urls::matches &matches) {
        using namespace boost::beast;
        using namespace std::chrono;
        int size = 5;
        std::error_code error;
        if (!request.body().empty()) {
            auto rootJson = boost::json::parse(request.body(), error);
            if (error) {
                LOG(info) << "<" << request.body() << "> parse json error: " << error.message();
            } else {
                auto &root = rootJson.as_object();
                if (root.contains("size")) {
                    size = root.at("size").as_int64();
                }
            }
        }
        auto database = Singleton<Older::Database>::instance();
        auto stats = database->latestViewedUrls(size + urlFilter.size());

        boost::json::array reply;
        int index = 0;
        for (auto &stat : stats) {
            if (std::find(urlFilter.cbegin(), urlFilter.cend(), stat.url) != urlFilter.cend()) continue;
            boost::json::object object;
            object["url"] = stat.url;
            object["time"] = stat.lastViewTime;
            reply.push_back(std::move(object));
            index++;
            if (index >= size) break;
        }
        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() = boost::json::serialize(reply);
        s.prepare_payload();
        session.reply(std::move(s));
    });
}

void live2dBackend() {
    using namespace Core;
    auto application = Singleton<Older::Application>::instance();
    application->insertUrl("/api/v1/live2d/{path*}", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                        const boost::urls::matches &matches) {
        auto settings = Singleton<Older::Settings>::instance();
        using namespace boost::beast;
        boost::urls::url_view view(request.target());
        auto target = view.path();
        // LOG(info) << target;
        if (target.find("..") != boost::beast::string_view::npos) {
            session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
            return;
        }
        std::string path = ResponseUtility::pathCat(settings->live2dModelsRoot(), matches["path"]);
        if (target.back() == '/') path.append("index.html");
        if (std::filesystem::is_directory(path)) path.append("/index.html");
        boost::beast::error_code ec;
        http::file_body::value_type body;
        body.open(path.c_str(), boost::beast::file_mode::scan, ec);
        if (ec == boost::beast::errc::no_such_file_or_directory) {
            std::ostringstream oss;
            oss << "The resource '" << target << "' was not found.";
            LOG(error) << oss.str();
            session.errorReply(request, http::status::not_found, oss.str());
            return;
        } else if (ec) {
            session.reply(ServiceLogic::serverError(request, ec.message()));
            return;
        }
        auto const size = body.size();
        http::response<http::file_body> res{std::piecewise_construct, std::make_tuple(std::move(body)),
                                            std::make_tuple(http::status::ok, request.version())};
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, ResponseUtility::mimeType(path));
        // res.set(http::field::access_control_allow_origin, "*");
        res.set(http::field::cache_control, "max-age=2592000");
        res.set(http::field::expires, "Fri, 22 Nov 2124 13:30:28 GMT");
        res.content_length(size);
        res.keep_alive(request.keep_alive());
        session.reply(std::move(res));
    });
}

void userAccount() {
    using namespace Core;
    auto application = Singleton<Older::Application>::instance();
    application->insertUrl("/api/v1/user/register", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                       const boost::urls::matches &matches) {
        auto rootJson = boost::json::parse(request.body());
        auto &root = rootJson.as_object();
        boost::json::object reply;
        if (root.contains("username") && root.contains("password") && root.contains("email")) {
            try {
                auto account = Older::Account::hashPassword(std::string(root.at("password").as_string()));
                account.username = root.at("username").as_string();
                account.email = root.at("email").as_string();
                auto database = Singleton<Older::Database>::instance();
                database->createUser(account);

                reply["code"] = 200;
                reply["message"] = "register success";
                account = database->user(account.username);

                boost::json::object data;
                data["user_id"] = account.id;
                reply["data"] = std::move(data);
            } catch (const std::exception &e) {
                reply["code"] = 400;
                reply["message"] = e.what();
            }

        } else {
            reply["code"] = 400;
            reply["message"] = "missing username, password or email";
        }
        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() = boost::json::serialize(reply);
        s.prepare_payload();
        session.reply(std::move(s));
    });

    application->insertUrl("/api/v1/user/login", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                    const boost::urls::matches &matches) {
        auto rootJson = boost::json::parse(request.body());
        auto &root = rootJson.as_object();
        boost::json::object reply;
        std::string cookie;
        if (root.contains("username") && root.contains("password")) {
            try {
                auto database = Singleton<Older::Database>::instance();
                auto sessions = Singleton<Older::SessionStore>::instance();
                auto account = database->user(std::string(root.at("username").as_string()));
                bool logined = Older::Account::verifyPassword(account, std::string(root.at("password").as_string()));

                reply["code"] = logined ? 200 : 404;
                reply["message"] = logined ? "login success" : "wrong password or user";
                account = database->user(account.username);
                if (logined) {
                    boost::json::object data;
                    data["user_id"] = account.id;
                    reply["data"] = std::move(data);
                    // clang-format off
                    cookie = "older_auth=" + sessions->addSession(account.id)
                    + "; Path=/"
                    + "; HttpOnly"          // 阻止JS访问
                    + "; Secure"            // 仅HTTPS传输
                    + "; SameSite=Strict"   // 防止CSRF
                    + "; Max-Age=86400";   // 24小时过期
                    // clang-format on
                }
            } catch (const std::exception &e) {
                reply["code"] = 400;
                reply["message"] = e.what();
            }
        } else {
            reply["code"] = 400;
            reply["message"] = "missing username, password or email";
        }
        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");
        if (!cookie.empty()) {
            s.set(http::field::set_cookie, cookie);
        }
        s.keep_alive(request.keep_alive());
        s.body() = boost::json::serialize(reply);
        s.prepare_payload();
        session.reply(std::move(s));
    });

    application->insertUrl("/api/v1/user/verify", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                     const boost::urls::matches &matches) {
        auto cookies = request.find(http::field::cookie);
        boost::json::object reply;
        std::string cookie;
        if (cookies == request.end()) {
            reply["code"] = 401;
            reply["message"] = "cookie is not exists";
        } else {
            auto sessions = Singleton<Older::SessionStore>::instance();
            auto database = Singleton<Older::Database>::instance();
            std::string accessToken = extractToken(cookies->value(), "older_auth");
            auto [valid, newAccessToken] = sessions->validateAndRefresh(accessToken);

            if (valid) {
                if (newAccessToken != accessToken) { // 需要刷新令牌
                    // clang-format off
                    cookie = "older_auth=" + newAccessToken
                    + "; Path=/"
                    + "; HttpOnly"          // 阻止JS访问
                    + "; Secure"            // 仅HTTPS传输
                    + "; SameSite=Strict"   // 防止CSRF
                    + "; Max-Age=86400";   // 24小时过期
                    // clang-format on
                }
                reply["code"] = 200;
                reply["message"] = "verify success";
                boost::json::object data;
                auto d = sessions->at(newAccessToken);
                auto account = database->user(d.userId);
                data["user_id"] = d.userId;
                data["username"] = account.username;
                data["email"] = account.email;
                reply["data"] = std::move(data);
            } else {
                reply["code"] = 401;
                reply["message"] = "cookie is invalid";
            }
        }

        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");
        if (!cookie.empty()) {
            s.set(http::field::set_cookie, cookie);
        }
        s.keep_alive(request.keep_alive());
        s.body() = boost::json::serialize(reply);
        s.prepare_payload();
        session.reply(std::move(s));
    });

    application->insertUrl("/api/v1/user/logout", [](Older::HttpSession &session, const Older::HttpRequest &request,
                                                     const boost::urls::matches &matches) {
        http::response<boost::beast::http::string_body> res{boost::beast::http::status::ok, request.version()};
        try {
            auto sessions = Singleton<Older::SessionStore>::instance();
            auto cookies = request.find(http::field::cookie);
            if (cookies != request.end()) {
                std::string accessToken = extractToken(cookies->value(), "older_auth");
                if (!accessToken.empty()) {
                    sessions->removeSession(accessToken);
                }
            }

            res.set(http::field::content_type, "application/json");
            res.set(http::field::set_cookie, "older_auth=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure");
            res.set(http::field::set_cookie,
                    "older_refresh_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure");
            res.body() = R"({"code": 401, "message": "logout successfully"})";
        } catch (const std::exception &e) {
            res.result(http::status::internal_server_error);
            res.body() = R"({"status": "error", "message": ")" + std::string(e.what()) + "\"}";
        }
        session.reply(std::move(res));
    });
}

} // namespace ServiceLogic
