#include "pch.h"

#include "legacy_cloud_services.h"

#include "app.h"
#include "canvas.h"
#include "legacy_app_dialog_services.h"
#include "legacy_canvas_view_services.h"
#include "legacy_document_session_services.h"
#include "legacy_document_open_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_dialog_cloud.h"
#include "node_progress_bar.h"
#include "util.h"

namespace pp::panopainter {
pp::foundation::Status execute_legacy_cloud_download_selection_action(
    App& app,
    pp::app::CloudDownloadSelectionAction action,
    NodeDialogCloud& dialog);

namespace {

class LegacyCloudWorker final {
public:
    LegacyCloudWorker()
        : worker_([this](std::stop_token stop_token) {
            run(stop_token);
        })
    {
    }

    ~LegacyCloudWorker()
    {
        shutdown();
    }

    void post(std::function<void()> task)
    {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            if (stopping_)
                return;
            tasks_.push_back(std::move(task));
        }
        cv_.notify_one();
    }

private:
    void shutdown()
    {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            stopping_ = true;
        }
        cv_.notify_all();
    }

    void run(std::stop_token stop_token)
    {
        for (;;) {
            std::function<void()> task;
            {
                std::unique_lock<std::mutex> lock(mutex_);
                cv_.wait(lock, [&] {
                    return stopping_ || stop_token.stop_requested() || !tasks_.empty();
                });
                if ((stopping_ || stop_token.stop_requested()) && tasks_.empty())
                    break;
                task = std::move(tasks_.front());
                tasks_.pop_front();
            }

            if (task) {
                try {
                    task();
                } catch (...) {
                    LOG("cloud worker task failed");
                }
            }
        }
    }

    std::mutex mutex_;
    std::condition_variable cv_;
    std::deque<std::function<void()>> tasks_;
    bool stopping_ = false;
    std::jthread worker_;
};

LegacyCloudWorker& cloud_worker()
{
    static LegacyCloudWorker worker;
    return worker;
}

#if WITH_CURL
int progress_callback_download(void* clientp, curl_off_t dltotal,
    curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
    (void)ultotal;
    (void)ulnow;
    auto* progress = static_cast<std::function<void(float)>*>(clientp);
    const auto plan = pp::app::plan_cloud_transfer_progress(
        static_cast<std::int64_t>(dltotal),
        static_cast<std::int64_t>(dlnow));
    if (progress != nullptr && *progress && plan.notify)
        (*progress)(plan.fraction);
    return 0;
}

int progress_callback_upload(void* clientp, curl_off_t dltotal,
    curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
    (void)dltotal;
    (void)dlnow;
    auto* progress = static_cast<std::function<void(float)>*>(clientp);
    const auto plan = pp::app::plan_cloud_transfer_progress(
        static_cast<std::int64_t>(ultotal),
        static_cast<std::int64_t>(ulnow));
    if (progress != nullptr && *progress && plan.notify)
        (*progress)(plan.fraction);
    return 0;
}

struct curl_httppost* create_cloud_upload_form(const std::string& filename)
{
    struct curl_httppost* formpost = NULL;
    struct curl_httppost* lastptr = NULL;

    curl_formadd(&formpost,
        &lastptr,
        CURLFORM_COPYNAME, "fileToUpload",
        CURLFORM_FILE, filename.c_str(),
        CURLFORM_END);

    return formpost;
}

void handle_cloud_upload_transfer_result(CURLcode err, const std::string& res)
{
    (void)err;
    std::cout << "\n\nUPLOAD RESULT\n" << res << "\n\n\n";
}

void execute_cloud_download_transfer(
    App& app,
    std::string url,
    std::string dest_filepath,
    std::function<void(float)> progress);

void execute_cloud_download_flow(
    App& app,
    const pp::app::CloudDownloadRequest& request);

std::shared_ptr<NodeMessageBox> create_cloud_download_progress_dialog(App& app)
{
    return pp::panopainter::create_legacy_app_message_dialog(
        app,
        pp::app::plan_cloud_download_progress_prompt());
}

void execute_cloud_download_transfer_with_progress_dialog(
    App& app,
    const pp::app::CloudDownloadRequest& request,
    const std::shared_ptr<NodeMessageBox>& dialog)
{
    const std::string url = "https://panopainter.com/cloud/cloud-dwl.php?file=" + request.selected_file;
    execute_cloud_download_transfer(app, url, request.selected_path, [dialog](float p) {
        const auto progress = pp::app::format_cloud_download_progress_message(p);
        dialog->m_message->set_text(progress.c_str());
    });
}

void execute_cloud_download_transfer(
    App& app,
    std::string url,
    std::string dest_filepath,
    std::function<void(float)> progress)
{
    const auto plan = pp::app::plan_cloud_download_transfer(
        url,
        dest_filepath,
        progress != nullptr,
        app.disables_network_tls_verification());
    if (plan.action != pp::app::CloudTransferAction::start_transfer) {
        LOG("download skipped: invalid transfer request");
        return;
    }

    CURL* curl = curl_easy_init();
    if (curl)
    {
        FILE* fp = fopen(dest_filepath.c_str(), "wb");
        if (fp == nullptr) {
            LOG("download failed to open destination %s", dest_filepath.c_str());
            curl_easy_cleanup(curl);
            return;
        }
        LOG("download %s to %s", url.c_str(), dest_filepath.c_str());
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_write);
        if (plan.disable_tls_verification)
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        if (plan.enable_progress)
        {
            curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_download);
            curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress);
            curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
        }
        auto err = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        fclose(fp);
    }
}

void open_downloaded_project_and_close_dialog(
    App& app,
    const pp::app::CloudDownloadRequest& request,
    const std::shared_ptr<NodeMessageBox>& dialog)
{
    execute_legacy_downloaded_project_open(app, request.selected_path, request.selected_name);
    pp::panopainter::close_legacy_dialog_node(*dialog);
}

void execute_cloud_download_flow(
    App& app,
    const pp::app::CloudDownloadRequest& request)
{
    auto m = create_cloud_download_progress_dialog(app);
    execute_cloud_download_transfer_with_progress_dialog(app, request, m);
    open_downloaded_project_and_close_dialog(app, request, m);
}

void execute_cloud_upload_transfer(
    App& app,
    std::string filename,
    std::string name,
    std::function<void(float)> progress)
{
    const auto plan = pp::app::plan_cloud_upload_transfer(
        filename,
        progress != nullptr,
        app.disables_network_tls_verification());
    if (plan.action != pp::app::CloudTransferAction::start_transfer) {
        LOG("upload skipped: invalid transfer request");
        return;
    }

    CURL* curl;
    auto* formpost = create_cloud_upload_form(filename);

    //curl_global_init(CURL_GLOBAL_ALL);

    curl = curl_easy_init();
    std::string res;

    if (curl)
    {
        std::string url = "https://panopainter.com/cloud/cloud-upl.php?name=" + name;
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &res);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler);
        if (plan.disable_tls_verification)
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        if (plan.enable_progress)
        {
            curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_upload);
            curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress);
            curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
        }
        auto err = curl_easy_perform(curl);
        handle_cloud_upload_transfer_result(err, res);
        curl_easy_cleanup(curl);
    }
}

void execute_cloud_publish_transfer_and_success_prompt(
    App& app,
    bool save_before_upload)
{
    if (save_before_upload)
    {
        execute_legacy_document_save_before_cloud_upload(app);
    }

    const auto progress_plan = pp::app::plan_cloud_upload_progress_dialog();
    auto pb = app.show_progress(progress_plan.title, progress_plan.total);

    execute_cloud_upload_transfer(app, app.doc_path, app.doc_filename, [pb](float p) {
        pb->set_progress(p);
    });

    pp::panopainter::close_legacy_dialog_node(*pb);
    const auto success_plan = pp::app::plan_cloud_upload_success_prompt();
    app.message_box(success_plan.title, success_plan.message, success_plan.show_cancel);
}

void execute_cloud_download_thread(
    App& app,
    const pp::app::CloudDownloadRequest& request)
{
    BT_SetTerminate();
    execute_cloud_download_flow(app, request);
}

void execute_cloud_publish_worker(App& app, bool save_before_upload)
{
    BT_SetTerminate();
    execute_cloud_publish_transfer_and_success_prompt(app, save_before_upload);
}

void wire_cloud_publish_prompt_buttons(
    const std::shared_ptr<NodeMessageBox>& dialog,
    std::function<void()> upload_thread)
{
    dialog->btn_ok->on_click = [dialog, upload_thread](Node*) {
        upload_thread();
        pp::panopainter::close_legacy_dialog_node(*dialog);
    };
    dialog->btn_cancel->on_click = [dialog](Node*) {
        pp::panopainter::close_legacy_dialog_node(*dialog);
    };
}

void setup_cloud_publish_prompt(App& app, bool save_before_upload)
{
    const auto prompt_plan = pp::app::plan_cloud_publish_prompt();
    auto dialog = app.message_box(prompt_plan.title, prompt_plan.message, prompt_plan.show_cancel);
    wire_cloud_publish_prompt_buttons(dialog, [&app, save_before_upload] {
        queue_legacy_cloud_worker_task([app = &app, save_before_upload] {
            execute_cloud_publish_worker(*app, save_before_upload);
        });
    });
}

void show_cloud_publish_prompt(App& app, bool save_before_upload)
{
    setup_cloud_publish_prompt(app, save_before_upload);
}

std::shared_ptr<NodeProgressBar> create_cloud_bulk_upload_progress(
    App& app,
    int progress_total)
{
    const auto progress_plan = pp::app::plan_cloud_bulk_upload_progress_dialog(progress_total);
    return app.show_progress(progress_plan.title, progress_plan.total);
}

void execute_cloud_bulk_upload_files(
    App& app,
    const std::vector<std::string>& names,
    const std::shared_ptr<NodeProgressBar>& bulk_progress)
{
    [[maybe_unused]] gl_state gl;
    for (const auto& n : names)
    {
        std::string path = app.data_path + "/" + n;
        execute_cloud_upload_transfer(app, path, std::string(), std::function<void(float)> {});

        if (bulk_progress) {
            bulk_progress->increment();
        }
    }
}

void close_cloud_bulk_upload_progress(std::shared_ptr<NodeProgressBar>& progress)
{
    if (progress) {
        pp::panopainter::close_legacy_dialog_node(*progress);
    }
    progress.reset();
}
#endif //WITH_CURL

void wire_cloud_browser_ok_button(
    const std::shared_ptr<NodeDialogCloud>& dialog,
    App* app)
{
    dialog->btn_ok->on_click = [app, dialog](Node*) {
        const auto selection_plan = pp::app::plan_cloud_download_selection(dialog->selected_file);
        const auto status = execute_legacy_cloud_download_selection_action(*app, selection_plan, *dialog);
        if (!status.ok())
            LOG("Cloud download selection action failed: %s", status.message);
    };
}

void show_cloud_browser_dialog(App& app)
{
    auto dialog = pp::panopainter::create_legacy_cloud_browser_dialog_overlay(app);
    wire_cloud_browser_ok_button(dialog, &app);
}

void show_cloud_save_required_warning(App& app);

class LegacyCloudServices final : public pp::app::CloudServices {
public:
    explicit LegacyCloudServices(App& app) noexcept
        : app_(app)
    {
    }

    void show_save_required_warning() override
    {
        show_cloud_save_required_warning(app_);
    }

    void prompt_publish(bool save_before_upload) override
    {
        show_cloud_publish_prompt(app_, save_before_upload);
    }

    void begin_bulk_upload(int progress_total, bool show_progress) override
    {
        bulk_progress_.reset();
        if (show_progress) {
            bulk_progress_ = create_cloud_bulk_upload_progress(app_, progress_total);
        }
    }

    void upload_all_bulk_files() override
    {
        auto names = Asset::list_files(app_.data_path, ".*\\.ppi");
        execute_cloud_bulk_upload_files(app_, names, bulk_progress_);
    }

    void end_bulk_upload() override
    {
        close_cloud_bulk_upload_progress(bulk_progress_);
    }

    void show_browser() override
    {
        show_cloud_browser_dialog(app_);
    }

    void start_download(const pp::app::CloudDownloadRequest& request) override
    {
        queue_legacy_cloud_worker_task([app = &app_, request] {
            execute_cloud_download_thread(*app, request);
        });
    }

private:
    App& app_;
    std::shared_ptr<NodeProgressBar> bulk_progress_;
};

void show_cloud_save_required_warning(App& app)
{
    const auto plan = pp::app::plan_cloud_save_required_prompt();
    app.message_box(plan.title, plan.message, plan.show_cancel);
}

} // namespace

void queue_legacy_cloud_worker_task(std::function<void()> task)
{
    cloud_worker().post(std::move(task));
}

pp::foundation::Status execute_legacy_cloud_upload_plan(
    App& app,
    const pp::app::CloudUploadPlan& plan)
{
    LegacyCloudServices services(app);
    return pp::app::execute_cloud_upload_plan(plan, services);
}

pp::foundation::Status execute_legacy_cloud_bulk_upload_plan(
    App& app,
    const pp::app::CloudBulkUploadPlan& plan)
{
    LegacyCloudServices services(app);
    return pp::app::execute_cloud_bulk_upload_plan(plan, services);
}

pp::foundation::Status execute_legacy_cloud_browse_action(
    App& app,
    pp::app::CloudBrowseAction action)
{
    LegacyCloudServices services(app);
    return pp::app::execute_cloud_browse_action(action, services);
}

pp::foundation::Status execute_legacy_cloud_download_selection_action(
    App& app,
    pp::app::CloudDownloadSelectionAction action,
    NodeDialogCloud& dialog)
{
    pp::app::CloudDownloadRequest request {
        .selected_file = dialog.selected_file,
        .selected_path = dialog.selected_path,
        .selected_name = dialog.selected_name,
    };
    if (action == pp::app::CloudDownloadSelectionAction::start_download) {
        pp::panopainter::close_legacy_dialog_node(dialog);
    }
    LegacyCloudServices services(app);
    return pp::app::execute_cloud_download_selection_action(action, services, request);
}

} // namespace pp::panopainter
