
#include "Global.h"
#include "Performance_Monitor.h"

#include <algorithm>
#include <cctype>
#include <string_view>

namespace {

constexpr std::size_t kMaxLogRequestBodySize = 8 * 1024;

void Log_Request_Body_If_Small(const drogon::HttpRequestPtr& request) {
    const auto& body = request->getBody();
    if (body.empty()) {
        return;
    }

    if (body.size() <= kMaxLogRequestBodySize) {
        LOG_INFO
            << "[request body] "
            << "path=" << request->path()
            << ", size=" << body.size()
            << ", body=" << std::string(body.data(), body.size());
    }
    else {
        LOG_INFO
            << "[request body skipped] "
            << "path=" << request->path()
            << ", size=" << body.size()
            << ", max=" << kMaxLogRequestBodySize;
    }
}

bool Is_Invalid_Http_Param(const std::exception& e) {
    return dynamic_cast<const Invalid_Http_Param*>(&e) != nullptr
        || dynamic_cast<const Psc::json_assign_error*>(&e) != nullptr
        || dynamic_cast<const std::invalid_argument*>(&e) != nullptr;
}

bool Is_Safe_Version_Directory_Name(std::string_view name) {
    if (name.empty() || name == "." || name == "..") {
        return false;
    }
    return std::ranges::all_of(name, [](unsigned char ch) {
        return std::isalnum(ch) != 0 || ch == '.' || ch == '_' || ch == '-';
    });
}

bool Is_Safe_Archive_Extension(std::string_view extension) {
    if (extension.empty() || extension.front() == '.' || extension.back() == '.') {
        return false;
    }
    return std::ranges::all_of(extension, [](unsigned char ch) {
        return std::isalnum(ch) != 0 || ch == '.' || ch == '_' || ch == '-';
    });
}

std::string Normalize_Archive_Extension(std::string extension, std::string fallback = "zip") {
    while (!extension.empty() && extension.front() == '.') {
        extension.erase(extension.begin());
    }
    if (extension.empty()) {
        extension = std::move(fallback);
    }
    if (!Is_Safe_Archive_Extension(extension)) {
        throw std::invalid_argument("invalid archive extension: " + extension);
    }
    return extension;
}

std::optional<std::string> Try_Get_String_Field(const JSON& object, const std::string& key) {
    const JSON* value = object.get(key);
    if (value == nullptr) {
        return std::nullopt;
    }
    if (value->valueType != Psc::String) {
        throw std::invalid_argument(key + " must be a string");
    }
    return value->val;
}

struct Archive_Export_Request {
    std::string version;
    Archive_Write_Options archive;
};

Archive_Export_Request Parse_Archive_Export_Request(
    const drogon::HttpRequestPtr& req,
    std::string default_version
) {
    Archive_Export_Request result;
    result.version = std::move(default_version);

    const auto& body = req->getBody();
    if (body.empty()) {
        return result;
    }

    auto parsed = Psc::try_parse_json(body.data());
    if (!parsed.has_value()) {
        throw_invalid_http_param("request body is not valid JSON");
    }

    const JSON& params = parsed.value();
    if (auto version = Try_Get_String_Field(params, "version")) {
        if (!version->empty()) {
            result.version = *version;
        }
    }
    if (!Is_Safe_Version_Directory_Name(result.version)) {
        throw std::invalid_argument("invalid version name: " + result.version);
    }

    if (auto format = Try_Get_String_Field(params, "format")) {
        if (!format->empty()) {
            result.archive.format = *format;
        }
    }
    if (auto filter = Try_Get_String_Field(params, "filter")) {
        result.archive.filter = *filter;
    }
    if (auto extension = Try_Get_String_Field(params, "extension")) {
        if (!extension->empty()) {
            result.archive.extension = Normalize_Archive_Extension(*extension);
        }
    }
    else {
        result.archive.extension = Normalize_Archive_Extension(result.archive.extension);
    }

    return result;
}

const Archive_Layout_Rules& Upgrade_Zip_Layout_Rules() {
    static const Archive_Layout_Rules rules = [] {
        Archive_Layout_Rules value;
        value.directories = {
            {
                "",
                {"adsb_cfg.ini", "config.json", "ecap_server", "lib9002config.json"},
                {"lib9002", "wwwroot"},
                {},
                {}
            },
            {
                "lib9002",
                {"lib9002.so", "testserver"},
                {"env"},
                {},
                {}
            }
        };
        return value;
    }();
    return rules;
}

} // namespace

void setupCors();
void Global::init_web_server() {
    register_http_performance_monitor(
        http_monitor_config.access_log,
        http_monitor_config.slow_request_threshold_ms);
    setupCors();
    static auto &web_server = web_server_config;
    Web_Server  &svr = this->svr;
    auto        &da = drogon::app();
    auto         return_file = [](const std::string &path) {
        auto res = drogon::HttpResponse::newHttpResponse();
        if (std::filesystem::exists(path)) {
            std::string content;
            httplib::detail::read_file(path, content);
            auto content_type = httplib::detail::find_content_type(path, {}, "application/octet-stream");
            res->setContentTypeString(content_type);
            res->setBody(content);
            // res.set_file_content(path);
        } else {
            res->setStatusCode(drogon::k404NotFound);
            JSON ret = JSON::object();
            ret.append({"LOG_POS", LOG_POS});
            ret.append({"not_found_path", path});
            res->setContentTypeCode(drogon::CT_TEXT_PLAIN);
            res->setBody(ret.to_json_string());
        }
        return res;
    };
    auto add_path = [return_file](const std::string &path) {
        drogon::app().registerHandlerViaRegex(
                path,
                [return_file](const drogon::HttpRequestPtr                          &req,
                              std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
                    callback(return_file(web_server.get_true_webapp() + "/index.html"));
                },
                {drogon::Get});
    };


    drogon::app().registerHandlerViaRegex(
            R"(^/tiles/.*$)",
            [return_file](const drogon::HttpRequestPtr                          &req,
                          std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
                auto path = web_server.get_true_tiles() + req->getPath().substr(6);
                callback(return_file(path));
            },
            {drogon::Get});


    drogon::app().registerHandlerViaRegex(
            "/",
            [return_file](const drogon::HttpRequestPtr                          &req,
                          std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
                callback(return_file(web_server.get_true_webapp() + "/index.html"));
            },
            {drogon::Get});


    add_path(R"(^/settings)");
    add_path(R"(^/map)");
    add_path(R"(^/dsp)");
    add_path(R"(^/aircraftlist)");
    drogon::app().registerHandlerViaRegex(
            R"(^/ui(/.*)?$)", [return_file](const drogon::HttpRequestPtr                          &req,
                                            std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
                std::cout << req->path() << std::endl;
                auto res = drogon::HttpResponse::newHttpResponse();
                auto path = web_server.get_true_webapp();
                auto rp = "/" + req->path().substr(3);
                if (rp == "/") {
                    path += "/index.html";
                } else {
                    path += rp;
                }
                callback(return_file(path));
            });

    State_Report::instance();
    svr.Get(R"(/get_debug_info)", [](HTTP_Param) {
        JSON  arr = JSON::array();
        auto &sp = Global::instance()->mode_acs.data_feed_config.pool_;
        arr.append(JSON::object({{"remain_size", sp.remain_size()}, {"free_size", sp.free_size()}}));
        res->setBody(arr.to_json_string("", "\n", ""));
    });


    mlat.server(this);
    database_server(this);
    external_resources_manager.server(this);
    device_config.server(this);
    mode_acs.server(this);
    dsp_config.server(this);


    // s.w.init();

    drogon::app().registerHandler(
        api + "export_logs",
        [](const drogon::HttpRequestPtr& req,
           std::function<void(const drogon::HttpResponsePtr&)>&& cb) {
            try {
                auto export_request = Parse_Archive_Export_Request(req, Global::instance()->version);
                const std::string& name = export_request.version;
                auto version_dir = std::filesystem::path(get_exe_dir() + "/..");
                std::string data = archive_directory_to_memory(version_dir /name/"logs",
                    {},
                    export_request.archive
                );
                auto resp = drogon::HttpResponse::newHttpResponse();
                resp->setBody(data);
                resp->setContentTypeString("application/octet-stream");
                resp->addHeader(
                    "Content-Disposition",
                    "attachment; filename*=UTF-8''" + name + "_logs." + export_request.archive.extension
                );
                resp->addHeader(
             "ok", "true"
             );
                cb(resp);
            }



            catch (const std::exception& e) {
                auto resp = drogon::HttpResponse::newHttpResponse();
                resp->addHeader(
                "ok", "false"
                );
                Psc::JSON json = JSON::object({
                    {"message", Psc::platform_2_utf8(e.what()) }
                });
                resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
                resp->setBody(json.to_json_string());
                cb(resp);
            }
        }
    );

    drogon::app().registerHandler(
        api + "export_version",
        [](const drogon::HttpRequestPtr& req,
           std::function<void(const drogon::HttpResponsePtr&)>&& cb) {
            try {
                auto export_request = Parse_Archive_Export_Request(req, Global::instance()->version);
                const std::string& name = export_request.version;
                auto version_dir = std::filesystem::path(get_exe_dir() + "/..");

                std::cout<< "准备压缩:" << std::endl;
                std::string data = archive_directory_to_memory(version_dir /name,
                {
                    "logs/",
                    "uploads/",
                    "drogon_uploads/",
                    "backups/", // ghw的什么文件路径
                    "stack_trace.txt",
                    "pt.log",
                    "ecap_server.log",
                    "Core",
                    "core",
                    name + ".zip",
                    name + "." + export_request.archive.extension
                },
                    export_request.archive
                );
                std::cout<< "压缩成功:" << data.size() << std::endl;
                auto resp = drogon::HttpResponse::newHttpResponse();
                resp->setBody(data);
                resp->setContentTypeString("application/octet-stream");
                resp->addHeader(
                    "Content-Disposition",
                    "attachment; filename*=UTF-8''" + name + "." + export_request.archive.extension
                );
                resp->addHeader(
                "ok", "true"
                );
                cb(resp);
            }
            catch (const std::exception& e) {
                auto message = Psc::platform_2_utf8(e.what());
                std::cout << "导出异常触发:" << message << "   " << LOG_POS << std::endl;
                auto resp = drogon::HttpResponse::newHttpResponse();

                Psc::JSON json = JSON::object({
                    {"message", message}
                });
                resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
                resp->setBody(json.to_json_string());
                resp->addHeader(
                "ok", "false"
                );
                cb(resp);
            }
        }
    );




    drogon::app().registerHandler(
    api + "restart_device",[]        (const drogon::HttpRequestPtr& req,
         std::function<void(const drogon::HttpResponsePtr&)>&& cb) {

            #ifdef __linux__
                std::string root_dir = Psc::get_exe_dir() + "/..";

                std::string log_file = fmt::format("{}/restart_device.log", root_dir);
                std::string cmd = fmt::format(
                R"(systemd-run --scope --slice=system.slice sudo bash "{}/restart_device.bash" > {} 2>&1 &)",
                root_dir,
                log_file
                );
                std::cout << "Generated Command: " << cmd << std::endl;
                std::system(cmd.c_str());
            #endif
            auto resp = drogon::HttpResponse::newHttpResponse();
            resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
            resp->setBody(R"({"ok":true})");
            cb(resp);

        }
    );


    drogon::app().registerHandler(
        api + "import_version",
        []
        (const drogon::HttpRequestPtr& req,
         std::function<void(const drogon::HttpResponsePtr&)>&& cb)
        {
            try {
                // ---------- 参数校验 ----------
                auto dir = req->getHeader("dir");
                if (!Is_Safe_Version_Directory_Name(dir)) {
                    throw std::invalid_argument("invalid or missing header: dir");
                }

                const auto& body = req->getBody();
                auto archive_layout_result = validate_archive_layout_from_memory(
                    body.data(),
                    body.size(),
                    Upgrade_Zip_Layout_Rules()
                );
                if (!archive_layout_result) {
                    throw Invalid_Archive_Error(archive_layout_result.error());
                }

                auto version_dir = std::filesystem::path(get_exe_dir() + "/..");
                // ---------- 路径准备 ----------
                auto targetDir =  version_dir / dir;

                std::cout << "========dir====" << dir << std::endl;
                std::cout << "========version_dir====" << version_dir << std::endl;
                std::cout << "========targetDir====" << targetDir << std::endl;

                std::filesystem::create_directories(targetDir);

                const auto archive_extension = Normalize_Archive_Extension(req->getHeader("archive-extension"));
                auto archivePath = targetDir / (dir + "." + archive_extension);

                // ---------- 写 zip ----------
                std::ofstream ofs(archivePath, std::ios::binary);
                if (!ofs) {
                    throw std::runtime_error("open archive failed: " + archivePath.string());
                }

                ofs.write(body.data(), body.size());
                if (!ofs) {
                    throw std::runtime_error("write archive failed: " + archivePath.string());
                }
                ofs.close();
                if (!ofs) {
                    throw std::runtime_error("close archive failed: " + archivePath.string());
                }




                #ifdef _WIN32
                     std::string root_dir = version_dir.string();
                     std::string old_version = Global::instance()->version; // Global version info
                     std::string new_version = dir;
                     std::string log_file = fmt::format("{}/upgrade_{}_to_{}.log", root_dir, old_version, new_version);
                     std::string cmd = fmt::format(
                         R"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {{ . '{}/upgrade.legacy' '{}' '{}' > '{}' 2>&1 }}" )",
                         root_dir,
                         old_version,
                         new_version,
                         log_file
                     );
                #else
                    std::string root_dir = version_dir.string();
                    std::string old_version = Global::instance()->version; // Global version info
                    std::string new_version = dir;
                    std::string log_file = fmt::format("{}/upgrade_{}_to_{}.log",root_dir, old_version,  new_version);
                    std::string cmd = fmt::format(
                        R"(systemd-run --scope --slice=system.slice sudo bash "{}/upgrade.bash" "{}" "{}" > {} 2>&1 &)",
                        root_dir,
                        old_version,
                        new_version,
                        log_file
                    );
                #endif
                std::cout << "Generated Command: " << cmd << std::endl;
                std::system(cmd.c_str());
                // ---------- 成功响应 ----------
                auto resp = drogon::HttpResponse::newHttpResponse();
                resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
                resp->setBody(R"({"ok":true})");
                cb(resp);
            }
            catch (const std::exception& e) {
                auto resp = drogon::HttpResponse::newHttpResponse();
                const bool invalid_input =
                    dynamic_cast<const Invalid_Archive_Error*>(&e) != nullptr
                    || dynamic_cast<const std::invalid_argument*>(&e) != nullptr;
                resp->setStatusCode(
                    invalid_input
                        ? drogon::k400BadRequest
                        : drogon::k500InternalServerError
                );
                resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
                auto reason = Psc::platform_2_utf8(e.what());
                std::cout << "异常" << reason << std::endl;
                Psc::JSON json = JSON::object({
                    {"ok", false},
                    {"error", reason}
                });
                resp->setBody(json.to_json_string());
                cb(resp);
            }
        },
        {drogon::Post}
    );


    svr.Post(api + "get_debug_log", [](HTTP_Param) {
        CHECK_JSON_PARAM
        HTTP_REQUIRE_VALUE(start_serial_number, params.try_get_number<Serial_Number_Type>("serial_number"))
        auto log = base64_encode(debug_logger_info_manager.get_info(start_serial_number));
        JSON ret = JSON::object();
        ret.append({"log", log});
        ret.append({"serial_number", debug_logger_info_manager.get_cur_serial_number()});
        ret.append({"start_serial_number", start_serial_number});
        res->setBody(warp(ret).to_json_string());
    });


    svr.Post(api + "get_general_info", [](HTTP_Param) {
        auto  g = Global::instance();
        // auto &db = *Global::instance()->total_source();
        auto  ret = JSON::object();
        // ret.append({"total_source key", db.key});
        // ret.append({"飞机总数", db.get_aircraft_num()});
        // ret.append({"解出位置的数量", db.have_pos_aircraft_num});
        JSON arr = JSON::object();
        for (auto &ds: g->mode_acs.data_source_config.map.list()) {
            if (!ds->enable) continue;
            arr.append({ds->key, ds->statistic_json()});
        }
        ret.append({"统计信息", arr});
        // ret.append({"基站信息", base_station.to_Json()});
        res->setBody(warp(ret).to_json_string());
    });




    svr.Post(api + "version", [this](HTTP_Param) {
        auto  ret = JSON::object();
        ret.append({"version", version});
        res->setBody(warp(ret).to_json_string());
    });


    auto now = std::chrono::system_clock::now();
    start_server_time = std::chrono::system_clock::to_time_t(now);
    svr.Post(api + "start_device_time", [this](HTTP_Param) {
        auto  ret = JSON::object();
        ret.append({"start_device_time", start_server_time});
        res->setBody(warp(ret).to_json_string());
    });

    da.setDefaultHandler(
        [](const drogon::HttpRequestPtr& req,
           std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
            LOG_WARN
                << "[http route not found] "
                << "path=" << req->path();

            auto res = drogon::HttpResponse::newHttpResponse();

            res->setStatusCode(drogon::k404NotFound);
            res->setBody("not found");
            res->addHeader("Content-Type", "text/plain; charset=utf-8");

            callback(res);
        }
    );

    da.setCustomErrorHandler(
        [](drogon::HttpStatusCode status,
           const drogon::HttpRequestPtr& req) -> drogon::HttpResponsePtr {
            LOG_WARN
                << "[http error] "
                << "path=" << req->path()
                << ", status=" << static_cast<int>(status);

            auto res = drogon::HttpResponse::newHttpResponse();

            res->setStatusCode(status);
            res->setBody("http error");
            res->addHeader("Content-Type", "text/plain; charset=utf-8");

            return res;
        }
    );

    da.setExceptionHandler(
        [](const std::exception& e,
           const drogon::HttpRequestPtr& req,
           std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
            auto res = drogon::HttpResponse::newHttpResponse();

            if (Is_Invalid_Http_Param(e)) {
                LOG_WARN
                    << "[invalid http parameters] "
                    << "path=" << req->path()
                    << ", detail=" << e.what();
                Log_Request_Body_If_Small(req);

                res->setStatusCode(drogon::k400BadRequest);
                res->setContentTypeCode(drogon::CT_APPLICATION_JSON);
                res->setBody(R"({"code":400,"message":"invalid http parameters"})");
            }
            else {
                LOG_ERROR
                    << "[handler exception] "
                    << "path=" << req->path()
                    << ", what=" << e.what();

                res->setStatusCode(drogon::k500InternalServerError);
                res->setContentTypeCode(drogon::CT_APPLICATION_JSON);
                res->setBody(R"({"code":500,"message":"internal server error"})");
            }

            callback(res);
        }
    );


    da.setUploadPath(get_exe_dir() + "/drogon_uploads");
    std::cout << "上传路径: " << da.getUploadPath() << std::endl;



    init_ok = true;
}

void setupCors() {
    // Register sync advice to handle CORS preflight (OPTIONS) requests
    drogon::app().registerSyncAdvice([](const drogon::HttpRequestPtr &req) -> drogon::HttpResponsePtr {
        if (req->method() == drogon::HttpMethod::Options) {
            auto resp = drogon::HttpResponse::newHttpResponse();

            // Set Access-Control-Allow-Origin header based on the Origin
            // request header
            const auto &origin = req->getHeader("Origin");
            if (!origin.empty()) {
                resp->addHeader("Access-Control-Allow-Origin", origin);
            }

            // Set Access-Control-Allow-Methods based on the requested method
            const auto &requestMethod = req->getHeader("Access-Control-Request-Method");
            if (!requestMethod.empty()) {
                resp->addHeader("Access-Control-Allow-Methods", requestMethod);
            }

            // Allow credentials to be included in cross-origin requests
            resp->addHeader("Access-Control-Allow-Credentials", "true");

            // Set allowed headers from the Access-Control-Request-Headers
            // header
            const auto &requestHeaders = req->getHeader("Access-Control-Request-Headers");
            if (!requestHeaders.empty()) {
                resp->addHeader("Access-Control-Allow-Headers", requestHeaders);
            }

            return std::move(resp);
        }
        return {};
    });

    // Register post-handling advice to add CORS headers to all responses
    drogon::app().registerPostHandlingAdvice(
            [](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) -> void {
                // Set Access-Control-Allow-Origin based on the Origin request
                // header
                const auto &origin = req->getHeader("Origin");
                if (!origin.empty()) {
                    resp->addHeader("Access-Control-Allow-Origin", origin);
                }

                // Reflect the requested Access-Control-Request-Method back in the
                // response
                const auto &requestMethod = req->getHeader("Access-Control-Request-Method");
                if (!requestMethod.empty()) {
                    resp->addHeader("Access-Control-Allow-Methods", requestMethod);
                }

                // Allow credentials to be included in cross-origin requests
                resp->addHeader("Access-Control-Allow-Credentials", "true");

                // Reflect the requested Access-Control-Request-Headers back
                const auto &requestHeaders = req->getHeader("Access-Control-Request-Headers");
                if (!requestHeaders.empty()) {
                    resp->addHeader("Access-Control-Allow-Headers", requestHeaders);
                }
            });
}
