#pragma once

#include "dps/config.hpp"
#include "dps/http.hpp"
#include "dps/json.hpp"

#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <filesystem>
#include <string>
#include <vector>

namespace dps {

struct User {
    int id = 0;
    std::string username;
    std::string password;
    int gold = 0;
    int level = 1;
    std::string qq;
    std::string name;
    std::string img;
    std::string client_key;
    std::string server_key;
};

struct UserAndServer {
    std::string user_name;
    std::string server_ip;
    std::string account;
    std::string password;
    std::string port;
};

struct UserServerProject {
    std::string server_ip;
    std::string project_name;
    std::string user_name;
    std::string expiration_date;
    int open = 1;
};

struct ProjectDefinition {
    std::string name;
    std::vector<std::string> script_paths;
    std::map<std::string, Json> info;
    int price = 0;
    bool is_private = false;
    std::string img_path;
};

class LegacyCrypto {
public:
    explicit LegacyCrypto(std::string jwt_secret);

    std::string issue_jwt(const std::string &username, long long expiration_seconds) const;
    std::optional<std::string> validate_jwt(const std::string &token) const;

    std::string hash_password(const std::string &password) const;
    bool verify_password(const std::string &password, const std::string &encoded) const;

    std::string rsa_private_encrypt_tool(const std::string &plain) const;
    std::string rsa_private_encrypt_script(const std::string &plain) const;
    std::string gzip_base64(const std::string &plain) const;
    std::vector<unsigned char> makecode(std::vector<unsigned char> bytes, const std::vector<int> &key) const;
    std::string xor_encrypt(const std::string &plain, const std::string &key) const;

private:
    std::string base64_url_encode(const std::string &input) const;
    std::string base64_url_decode(const std::string &input) const;
    std::string hmac_sha256(const std::string &message) const;
    std::string sha256_hex(const std::string &message) const;
    std::string bcrypt_hash(const std::string &password) const;
    std::string rsa_private_encrypt_der_b64(const std::string &der_b64, const std::string &plain) const;

    std::string jwt_secret_;
};

class Database {
public:
    explicit Database(const DatabaseConfig &config);
    ~Database();

    bool connect();
    bool is_connected() const;
    std::string last_error() const;
    std::string escape(const std::string &value) const;
    bool execute(const std::string &sql);
    std::vector<std::map<std::string, std::string>> query(const std::string &sql);
    long long last_insert_id() const;

private:
    struct Impl;
    std::unique_ptr<Impl> impl_;
};

class ProjectCatalog {
public:
    explicit ProjectCatalog(const PathConfig &paths);

    void reload();
    std::optional<ProjectDefinition> get_project(const std::string &name, const std::string &version = "") const;
    Json public_project_info() const;
    Json all_project_info() const;
    int project_price(const std::string &name) const;
    std::string project_img_path(const std::string &name) const;
    std::vector<std::string> base_script_contents(const std::string &version, bool cs_mode) const;
    std::vector<std::string> project_script_contents(const std::string &project_name, const std::string &version) const;
    std::vector<std::string> custom_client_scripts(const std::string &ip, const std::string &version) const;
    std::vector<std::pair<std::string, std::string>> dps_service_scripts(const std::string &ip) const;
    std::vector<std::pair<std::string, std::string>> dps_service_scripts_new(const std::string &ip, const std::string &version) const;
    std::string newest_version() const;
    long long base_update_time(const std::string &version) const;

private:
    struct VersionData {
        std::string name;
        std::map<std::string, ProjectDefinition> projects;
        std::vector<std::string> base_scripts;
        std::vector<std::string> cs_base_scripts;
        std::map<std::string, std::vector<std::string>> project_scripts;
        std::map<std::string, std::vector<std::string>> custom_client_scripts;
        Json dps_manifest;
    };

    const VersionData *find_version(const std::string &requested) const;
    std::vector<std::string> read_script_list(const std::string &root, const std::vector<std::string> &items) const;
    std::vector<std::pair<std::string, std::string>> read_named_scripts(const std::string &root, const std::vector<std::string> &items) const;
    void load_client_versions();
    void load_custom_client_scripts(VersionData &data);
    Json parse_json_file(const std::string &path) const;

    PathConfig paths_;
    std::map<std::string, VersionData> versions_;
    std::string newest_version_;
};

class Application {
public:
    explicit Application(AppConfig config);
    int run();

private:
    using Row = std::map<std::string, std::string>;

    void register_routes();
    Response handle_login(const Request &request);
    Response handle_register(const Request &request);
    Response handle_change_password(const Request &request);
    Response handle_get_info(const Request &request);
    Response handle_add_server(const Request &request);
    Response handle_delete_server(const Request &request);
    Response handle_get_server_list(const Request &request);
    Response handle_get_server(const Request &request);
    Response handle_get_project_info_list(const Request &request);
    Response handle_get_user_project_list(const Request &request);
    Response handle_get_server_project_list(const Request &request);
    Response handle_buy(const Request &request);
    Response handle_add_gold(const Request &request);
    Response handle_transfer(const Request &request);
    Response handle_set_key(const Request &request);
    Response handle_set_project_open(const Request &request);

    Response handle_rindro_download(const Request &request);
    Response handle_static_download(const std::string &disk_path, const std::string &download_name) const;
    Response handle_get_image(const Request &request, bool thumbnail);
    Response handle_get_mp42(const Request &request);
    Response handle_get_version(const Request &request);

    Response handle_dps_version(const std::string &relative_file) const;
    Response handle_dps_list(const std::string &relative_dir, const std::string &metadata_file) const;
    Response handle_dps_download(const Request &request, const std::string &base_dir) const;

    Response handle_script_client(const Request &request);
    Response handle_script_client2(const Request &request);
    Response handle_script_getzdy(const Request &request);
    Response handle_script_getpro(const Request &request);
    Response handle_script_getservice(const Request &request);
    Response handle_script_base_update_time(const Request &request);
    Response handle_script_base_sut(const Request &request);
    Response handle_script_getservice_new(const Request &request);

    Response handle_video_placeholder(const Request &request);
    Response handle_payment_placeholder(const Request &request);

    std::optional<User> find_user_by_username(const std::string &username);
    bool username_exists(const std::string &username);
    bool create_user(const std::string &username, const std::string &password, const std::string &qq);
    bool update_password(const std::string &username, const std::string &qq, const std::string &password);
    bool update_user_row(const User &user);
    std::vector<UserAndServer> servers_for_user(const std::string &username);
    std::optional<UserAndServer> find_server(const std::string &username, const std::string &ip);
    bool upsert_server(const UserAndServer &server);
    bool delete_server(const std::string &username, const std::string &ip);
    std::vector<UserServerProject> projects_for_ip(const std::string &ip, bool require_open = false);
    std::vector<UserServerProject> projects_for_user(const std::string &username);
    std::optional<UserServerProject> find_project_binding(const std::string &ip, const std::string &project_name);
    bool add_or_extend_project(const std::string &ip, const std::string &project_name, int days, const std::string &username);
    bool update_project_open(const std::string &ip, const std::string &project_name, int open_value);
    bool insert_log(const std::string &username, int type, const std::string &content);

    Response json_response(int status, const Json &body) const;
    Response common_success(const Json &data, const std::string &message = "操作成功", int status = 200) const;
    Response common_failed(int code, const std::string &message, int http_status = 200) const;
    Response file_response(const std::filesystem::path &path, const std::string &download_name = "") const;
    std::string request_ip(const Request &request) const;
    std::vector<int> random_key() const;
    std::string format_date_now() const;
    std::string add_days(const std::string &iso_date, int days) const;
    std::string make_random_name() const;
    Json parse_body_or_empty(const Request &request) const;
    std::optional<std::string> current_user(const Request &request) const;

    AppConfig config_;
    Router router_;
    LegacyCrypto crypto_;
    Database database_;
    ProjectCatalog catalog_;
    mutable std::mutex transfer_mutex_;
    mutable std::mutex script_mutex_;
    std::map<std::string, std::string> ip_and_mac_;
};

}  // namespace dps
