/*******************************************************************************
 * Survey WebEdit Plugin for Blogi
 *
 * Embeds a survey into a page via the visual editor.
 * The user selects a survey name (must exist in the survey_templates table).
 * On render, the survey form HTML + wizard JS is emitted inline.
 *******************************************************************************/

#include <sstream>
#include <iostream>
#include <cstring>

#include <tinyxml2.h>
#include <htmlpp/html.h>
#include <json-c/json.h>

#include "plugin.h"

extern "C" {
    namespace blogi::webedit {

        class SurveyWidget : public EditPlugin {
        public:
            SurveyWidget() {
                Type = "b83de5f1-9a27-4c3b-b1e6-7d45f8a2c901";
            }

            virtual ~SurveyWidget() = default;

            const std::string getName() const override { return "Survey"; }
            const std::string getAuthor() const override { return "Jan Koester"; }
            const std::string getVersion() const override { return "1.0"; }

            void logError(const std::string &message) override {
                std::cerr << "SurveyWidget Error (" << getInstanceId().c_str() << "): " << message << std::endl;
            }

        private:
            std::string surveyName;
            std::string surveyTitle = "Survey";
            std::string width = "";
            std::string height = "";
            bool hidden = false;
            bool m_hidden = false;
            std::string m_width = "";
            std::string m_height = "";
            std::string customCss = "";

        public:

            // --- UI Settings ---
            void Settings(libhttppp::HttpRequest &curreq, libhtmlpp::HtmlElement &settings) override {
                libhtmlpp::HtmlElement title("h3");
                title.appendChild(libhtmlpp::TextElement("Survey Settings"));
                settings.appendChild(title);

                libhtmlpp::HtmlElement label("label");
                label.appendChild(libhtmlpp::TextElement("Survey Name: "));
                settings.appendChild(label);

                libhtmlpp::HtmlElement input("input");
                input.setAttribute("type", "text");
                input.setAttribute("name", "survey_name");
                input.setAttribute("value", surveyName.c_str());
                settings.appendChild(input);
            }

            // --- JSON API ---
            void JsonApi(json_object *request, json_object *response) override {
                if (!request) { logError("null request"); return; }

                json_object *action_obj = nullptr;
                const char *action = nullptr;
                if (json_object_object_get_ex(request, "action", &action_obj))
                    action = json_object_get_string(action_obj);
                if (!action || !action[0]) { logError("no action"); return; }

                if (strcmp(action, "modify_bulk") == 0) {
                    json_object *props_obj = nullptr;
                    if (json_object_object_get_ex(request, "properties", &props_obj)) {
                        const char *s = json_object_to_json_string_ext(props_obj, JSON_C_TO_STRING_NOSLASHESCAPE);
                        if (s) fromPropertiesJson(s);
                    }
                } else if (strcmp(action, "read") == 0) {
                    json_object *result_obj = json_object_new_object();
                    json_object_object_add(result_obj, "properties", json_object_new_string(toPropertiesJson().c_str()));
                    if (response) json_object_object_add(response, "data", result_obj);
                } else if (strcmp(action, "get_schema") == 0) {
                    if (response) json_object_object_add(response, "schema", getSchemaJson());
                }

                if (response) {
                    json_object_object_add(response, "status", json_object_new_string("success"));
                    json_object_object_add(response, "plugin_type", json_object_new_string("Survey"));
                }
            }

            // --- Schema ---
            static json_object* getSchemaJson() {
                json_object *arr = json_object_new_array();
                auto addField = [&](const char *key, const char *label, const char *type,
                                    const char *placeholder = nullptr, const char *target = "all") {
                    json_object *f = json_object_new_object();
                    json_object_object_add(f, "key", json_object_new_string(key));
                    json_object_object_add(f, "label", json_object_new_string(label));
                    json_object_object_add(f, "type", json_object_new_string(type));
                    if (placeholder) json_object_object_add(f, "placeholder", json_object_new_string(placeholder));
                    json_object_object_add(f, "target", json_object_new_string(target));
                    json_object_array_add(arr, f);
                };
                addField("survey_name", "Survey Name", "survey_select", "kundenzufriedenheit");
                addField("survey_title", "Display Title", "text", "Ihre Meinung");
                addField("width", "Width", "text", "e.g. 100%", "desktop");
                addField("height", "Height", "text", "e.g. auto", "desktop");
                addField("hidden", "Hidden", "checkbox", nullptr, "desktop");
                {
                    json_object *hf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1);
                    json_object_object_add(hf, "default", json_object_new_boolean(false));
                }
                addField("custom_css", "Custom CSS", "textarea", ".we-xxx { color: red; }");
                return arr;
            }

            // --- XML Persistence ---
            tinyxml2::XMLElement* toXmlObject(tinyxml2::XMLDocument *doc) override {
                tinyxml2::XMLElement *el = doc->NewElement(getName().c_str());
                el->SetAttribute("instance_id", getInstanceId().c_str());
                el->SetAttribute("type_id", getTypeId().c_str());
                el->SetAttribute("survey_name", surveyName.c_str());
                el->SetAttribute("survey_title", surveyTitle.c_str());
                el->SetAttribute("width", width.c_str());
                el->SetAttribute("height", height.c_str());
                el->SetAttribute("hidden", hidden ? "true" : "false");
                el->SetAttribute("m_hidden", m_hidden ? "true" : "false");
                el->SetAttribute("m_width", m_width.c_str());
                el->SetAttribute("m_height", m_height.c_str());

                if (!customCss.empty()) {
                    tinyxml2::XMLElement* cssEl = doc->NewElement("CustomCss");
                    cssEl->SetText(customCss.c_str());
                    el->InsertEndChild(cssEl);
                }
                return el;
            }

            void fromXmlObject(tinyxml2::XMLElement *xml_data) override {
                const char *val = nullptr;
                val = xml_data->Attribute("survey_name");
                if (val) surveyName = val;
                val = xml_data->Attribute("survey_title");
                if (val) surveyTitle = val;
                val = xml_data->Attribute("width");
                if (val) width = val;
                val = xml_data->Attribute("height");
                if (val) height = val;
                val = xml_data->Attribute("hidden");
                if (val) hidden = (strcmp(val, "true") == 0);
                val = xml_data->Attribute("m_hidden");
                if (val) m_hidden = (strcmp(val, "true") == 0);
                val = xml_data->Attribute("m_width");
                if (val) m_width = val;
                val = xml_data->Attribute("m_height");
                if (val) m_height = val;

                tinyxml2::XMLElement* cssEl = xml_data->FirstChildElement("CustomCss");
                if (cssEl) {
                    const char* cssVal = cssEl->GetText();
                    if (cssVal) customCss = cssVal;
                }
            }

            // --- Properties JSON ---
            std::string toPropertiesJson() {
                json_object *obj = json_object_new_object();
                json_object_object_add(obj, "survey_name", json_object_new_string(surveyName.c_str()));
                json_object_object_add(obj, "survey_title", json_object_new_string(surveyTitle.c_str()));
                json_object_object_add(obj, "width", json_object_new_string(width.c_str()));
                json_object_object_add(obj, "height", json_object_new_string(height.c_str()));
                json_object_object_add(obj, "m_width", json_object_new_string(m_width.c_str()));
                json_object_object_add(obj, "m_height", json_object_new_string(m_height.c_str()));
                json_object_object_add(obj, "hidden", json_object_new_boolean(hidden));
                json_object_object_add(obj, "m_hidden", json_object_new_boolean(m_hidden));
                json_object_object_add(obj, "custom_css", json_object_new_string(customCss.c_str()));
                const char *result = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_NOSLASHESCAPE);
                std::string out(result);
                json_object_put(obj);
                return out;
            }

            void fromPropertiesJson(const std::string &json) {
                json_object *obj = json_tokener_parse(json.c_str());
                if (!obj) return;
                json_object *val_obj = nullptr;
                const char *val = nullptr;

                if (json_object_object_get_ex(obj, "survey_name", &val_obj)) {
                    val = json_object_get_string(val_obj);
                    if (val) surveyName = val;
                }
                if (json_object_object_get_ex(obj, "survey_title", &val_obj)) {
                    val = json_object_get_string(val_obj);
                    if (val) surveyTitle = val;
                }
                if (json_object_object_get_ex(obj, "width", &val_obj)) {
                    val = json_object_get_string(val_obj); if (val) width = val;
                }
                if (json_object_object_get_ex(obj, "height", &val_obj)) {
                    val = json_object_get_string(val_obj); if (val) height = val;
                }
                if (json_object_object_get_ex(obj, "m_width", &val_obj)) {
                    val = json_object_get_string(val_obj); if (val) m_width = val;
                }
                if (json_object_object_get_ex(obj, "m_height", &val_obj)) {
                    val = json_object_get_string(val_obj); if (val) m_height = val;
                }
                if (json_object_object_get_ex(obj, "hidden", &val_obj))
                    hidden = json_object_get_boolean(val_obj);
                if (json_object_object_get_ex(obj, "m_hidden", &val_obj))
                    m_hidden = json_object_get_boolean(val_obj);
                if (json_object_object_get_ex(obj, "custom_css", &val_obj)) {
                    val = json_object_get_string(val_obj);
                    if (val) customCss = val;
                }

                json_object_put(obj);
            }

            // --- HTML Rendering ---
            void Render(libhtmlpp::HtmlElement &el) const override {
                std::string responsiveClass = std::string("we-") + getInstanceId().c_str();
                std::string submitUrl = getPrefix() + "/survey/" + surveyName;

                // Wrapper div
                libhtmlpp::HtmlElement wrapper("div");
                wrapper.setAttribute("class", (responsiveClass + " survey-embed").c_str());
                std::string wrapStyle;
                if (hidden) wrapStyle += "display:none;";
                if (!width.empty()) wrapStyle += " width: " + width + ";";
                if (!height.empty()) wrapStyle += " height: " + height + ";";
                if (!wrapStyle.empty()) wrapper.setAttribute("style", wrapStyle.c_str());

                if (!customCss.empty()) {
                    libhtmlpp::HtmlElement cssStyle("style");
                    cssStyle.appendChild(libhtmlpp::TextElement(customCss.c_str()));
                    el.appendChild(cssStyle);
                }

                if (surveyName.empty()) {
                    libhtmlpp::HtmlElement msg("p");
                    msg.appendChild(libhtmlpp::TextElement("Survey: No survey configured."));
                    wrapper.appendChild(msg);
                    el.appendChild(wrapper);
                    return;
                }

                // Render the survey container with data attribute for JS init
                std::string containerId = "survey-" + std::string(getInstanceId().c_str());
                libhtmlpp::HtmlElement container("div");
                container.setAttribute("id", containerId.c_str());
                container.setAttribute("data-survey-url", submitUrl.c_str());
                container.setAttribute("data-survey-name", surveyName.c_str());

                libhtmlpp::HtmlElement loading("div");
                loading.setAttribute("class", "survey-loading");
                std::string loadText = surveyTitle.empty() ? "Loading survey..." : surveyTitle;
                loading.appendChild(libhtmlpp::TextElement(loadText.c_str()));
                container.appendChild(loading);

                wrapper.appendChild(container);

                // Mobile visibility
                if (m_hidden || hidden || !m_width.empty() || !m_height.empty()) {
                    libhtmlpp::HtmlElement mStyle("style");
                    std::string mCSS;
                    if (!m_width.empty()) mCSS += " width: " + m_width + " !important;";
                    if (!m_height.empty()) mCSS += " height: " + m_height + " !important;";
                    if (m_hidden) mCSS += "display:none !important;";
                    else if (hidden) mCSS += "display:block !important;";
                    std::string rule = "@media(max-width:768px){." + responsiveClass + "{" + mCSS + "}}";
                    mStyle.appendChild(libhtmlpp::TextElement(rule.c_str()));
                    wrapper.appendChild(mStyle);
                }

                // Inline JS: fetch the survey HTML from the survey plugin endpoint and inject it
                libhtmlpp::HtmlElement script("script");
                std::ostringstream js;
                js << "(function(){"
                   << "var c=document.getElementById('" << containerId << "');"
                   << "if(!c)return;"
                   << "var xhr=new XMLHttpRequest();"
                   << "xhr.open('GET','" << submitUrl << "',true);"
                   << "xhr.onreadystatechange=function(){"
                   << "if(xhr.readyState===4&&xhr.status===200){"
                   << "var doc=new DOMParser().parseFromString(xhr.responseText,'text/html');"
                   << "var main=doc.getElementById('content');"
                   << "if(main){c.innerHTML=main.innerHTML;}else{c.innerHTML=xhr.responseText;}"
                   << "var scripts=c.querySelectorAll('script');"
                   << "for(var i=0;i<scripts.length;i++){"
                   << "var s=document.createElement('script');s.textContent=scripts[i].textContent;"
                   << "scripts[i].parentNode.replaceChild(s,scripts[i]);}"
                   << "}else if(xhr.readyState===4){c.innerHTML='<p>Survey not found.</p>';}"
                   << "};xhr.send();"
                   << "})();";
                script.appendChild(libhtmlpp::TextElement(js.str().c_str()));
                wrapper.appendChild(script);

                el.appendChild(wrapper);
            }

            // --- Leaf node ---
            bool canHaveChildren() const override { return false; }
            bool haveChilds() override { return false; }
            void setChildElement(const EditPlugin &) override { logError("Survey cannot have children."); }
            void unsetChildElement(const EditPlugin &) override { logError("Survey cannot have children."); }
            void addNextElement(const EditPlugin &) override { logError("Survey does not support addNextElement."); }
            void remNextElement(const EditPlugin &) override { logError("Survey does not support remNextElement."); }
        };
    };
}

EXPORT blogi::webedit::SurveyWidget* create() {
    return new blogi::webedit::SurveyWidget();
}

EXPORT void destroy(blogi::webedit::SurveyWidget *p) {
    delete p;
}
