/*******************************************************************************
 * Copyright (c) 2024, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/



#include <iostream>
#include <algorithm>
#include <regex>
#include <chrono>

#include <htmlpp/html.h>
#include <htmlpp/exception.h>

#include <httppp/http.h>
#include <httppp/exception.h>

#include <dbpp/database.h>

#include <auth.h>
#include <conf.h>
#include <plugin.h>
#include <session.h>
#include <mail.h>
#include <blogi.h>
#include <i18n.h>
#include <compress.h>

#include <json-c/json.h>

namespace blogi {
    class Contact : public PluginApi{
    public:

        void initPlugin(){
            registerGPO("dac8d097-695d-42c4-88ea-0acfbe0ead6d",
                        "Blogi: Contact Settings",
                        "Controls access to contact plugin settings",
                        true);

            try {
                getConfig(*Args->database[0],"CONTACTMAIL",_Receiver);
            } catch(...) {}

            try {
                getConfig(*Args->database[0],"CONTACTPRIVACYURL",_PrivacyUrl);
            } catch(...) {}

            dbpp::SQL sql;
            dbpp::DBResult res;
            sql << "CREATE TABLE IF NOT EXISTS contact_pending ("
                << "token " << Args->database[0]->getUUIDType(sql) << " PRIMARY KEY,"
                << "name character varying(255) NOT NULL,"
                << "mail character varying(255) NOT NULL,"
                << "message text NOT NULL,"
                << "created_at character varying(64) NOT NULL"
                << ");";
            Args->database[0]->exec(sql, res);

            std::cout << "contact plugin loaded" <<std::endl;
        }

        bool haveSettings(){
            return true;
        }

        json_object* Settings(const int tid,libhttppp::HttpRequest &req,const std::string &sessionid){
            std::string _lang = getLang(*Args->database[tid]);
            bool saved = false;

            libhttppp::HttpForm form;
            form.parse(req);

            for(const auto &cur : form.urlData()){
                if(cur.key=="contactmail"){
                    setConfig(*Args->database[tid],"CONTACTMAIL",cur.value);
                    saved = true;
                }else if(cur.key=="contactprivacyurl"){
                    setConfig(*Args->database[tid],"CONTACTPRIVACYURL",cur.value);
                    saved = true;
                }
            }

            if(saved){
                try { getConfig(*Args->database[tid],"CONTACTMAIL",_Receiver); } catch(...){}
                try { getConfig(*Args->database[tid],"CONTACTPRIVACYURL",_PrivacyUrl); } catch(...){}
            }

            json_object *jroot = json_object_new_object();
            json_object_object_add(jroot, "title", json_object_new_string(tr(_lang,"Contact Settings").c_str()));

            if(saved){
                json_object_object_add(jroot, "message", json_object_new_string(tr(_lang,"Saved!").c_str()));
                json_object_object_add(jroot, "message_type", json_object_new_string("success"));
            }

            json_object *jform = json_object_new_object();
            json_object_object_add(jform, "method", json_object_new_string("POST"));
            json_object_object_add(jform, "action", json_object_new_string(
                Args->config->buildurl("settings/contact").c_str()));

            json_object *jfields = json_object_new_array();

            json_object *jf = json_object_new_object();
            json_object_object_add(jf, "name", json_object_new_string("contactmail"));
            json_object_object_add(jf, "label", json_object_new_string(tr(_lang,"Receiver Email").c_str()));
            json_object_object_add(jf, "type", json_object_new_string("text"));
            json_object_object_add(jf, "value", json_object_new_string(_Receiver.c_str()));
            json_object_array_add(jfields, jf);

            json_object *jpf = json_object_new_object();
            json_object_object_add(jpf, "name", json_object_new_string("contactprivacyurl"));
            json_object_object_add(jpf, "label", json_object_new_string(tr(_lang,"Privacy Policy URL").c_str()));
            json_object_object_add(jpf, "type", json_object_new_string("text"));
            json_object_object_add(jpf, "value", json_object_new_string(_PrivacyUrl.c_str()));
            json_object_array_add(jfields, jpf);

            json_object_object_add(jform, "fields", jfields);
            json_object_object_add(jform, "submit", json_object_new_string(tr(_lang,"save").c_str()));
            json_object_object_add(jroot, "form", jform);

            return jroot;
        }

        bool Controller(const int tid,libhttppp::HttpRequest &req,libhtmlpp::HtmlElement *page,const std::string &sessionid){

            std::string verifyUrl = Args->config->buildurl("contact/verify");

            if(req.getRequestURL().compare(0, verifyUrl.length(), verifyUrl) == 0){
                return handleVerify(tid, req, page, sessionid);
            }

            libhttppp::HttpForm curform;
            curform.parse(req);

            std::string name,mail,message;
            bool consent = false;

            for (const auto &cururlform : curform.urlData()) {
                    if(cururlform.key=="name" && !cururlform.value.empty()){
                        name=cururlform.value;
                    }else if(cururlform.key=="mail" && !cururlform.value.empty()){
                        mail=cururlform.value;
                    }else if(cururlform.key=="message" && !cururlform.value.empty()){
                        std::copy(cururlform.value.begin(),cururlform.value.end(),std::back_inserter(message));
                    }else if(cururlform.key=="privacy_consent"){
                        consent = true;
                    }

            }

            libhtmlpp::HtmlString cform,out;
            std::string _lang = getLang(*Args->database[tid]);

            static const std::regex emailRegex(R"(^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$)");
            bool validMail = !mail.empty() && std::regex_match(mail, emailRegex);

            if(validMail && !name.empty() && !message.empty() && !sessionid.empty() && consent){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> sbuf;

                sql << "SELECT COUNT(*) FROM contact_pending WHERE mail='"
                    << sql.escaped(sbuf, mail.c_str()) << "'";
                Args->database[tid]->exec(sql, res);
                int pendingCount = std::stoi(res[0][0]);

                if(pendingCount >= 5){
                    cform << "<div id=\"contactform\"><span class=\"error\">"
                          << tr(_lang,"Too many pending verification emails for this address. Please verify an existing email or try again later.")
                          << "</span></div>";
                }else{
                uuid::uuid token;
                token.generate();

                auto now = std::chrono::system_clock::now();
                auto epoch = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
                std::string createdAt = std::to_string(epoch);

                sql.clear();

                sql << "INSERT INTO contact_pending (token,name,mail,message,created_at) VALUES('"
                    << token.c_str() << "','"
                    << sql.escaped(sbuf, name.c_str()) << "','"
                    << sql.escaped(sbuf, mail.c_str()) << "','"
                    << sql.escaped(sbuf, message.c_str()) << "','"
                    << createdAt.c_str() << "')";
                Args->database[tid]->exec(sql, res);

                std::string link = Args->config->getsiteurl(); link+="/contact/verify?token=";
                            link += token.c_str();

                std::string verifyBody;
                verifyBody += "<html><body>";
                verifyBody += "<p>" + tr(_lang,"Please click the link below to confirm sending your contact message:") + "</p>";
                verifyBody += "<p><a href=\"" + link + "\">" + tr(_lang,"Confirm my message") + "</a></p>";
                verifyBody += "<p>" + tr(_lang,"If you did not submit a contact form, please ignore this email.") + "</p>";
                verifyBody += "</body></html>";

                sendHtmlMail(Args->smtp.get(), mail.c_str(),
                             tr(_lang,"Please verify your email address").c_str(), verifyBody);

                cform << "<div id=\"contactform\" ><span>"
                      << tr(_lang,"A verification email has been sent to your address. Please check your inbox and click the link to send your message.")
                      << "</span></div>";
                }
            }else if(!mail.empty() && !validMail && !sessionid.empty()){
                cform << "<div id=\"contactform\" ><span class=\"error\">" << tr(_lang,"Please enter a valid email address.") << "</span>";
                cform << "<form method=\"post\" action=\"" << Args->config->buildurl("contact")
                <<"\" ><label for=\"name\">" << tr(_lang,"Name:") << " </label> <input type=\"text\" id=\"name\" name=\"name\" value=\"" << name << "\" >"
                << "<label for=\"mail\">" << tr(_lang,"Email:") << "</label>"
                << " <input id=\"mail\" name=\"mail\" type=\"email\" value=\"" << mail << "\" ><br><label for=\"message\">" << tr(_lang,"Message:") << "</label><textarea id=\"message\""
                << " name=\"message\" >" << message << "</textarea>";
                cform << "<div class=\"privacy-consent\"><input type=\"checkbox\" id=\"privacy_consent\" name=\"privacy_consent\" required>";
                cform << "<label for=\"privacy_consent\">" << tr(_lang,"I agree that my data will be processed to handle my inquiry.");
                if(!_PrivacyUrl.empty()){
                    cform << " <a href=\"" << _PrivacyUrl << "\" target=\"_blank\">" << tr(_lang,"Privacy Policy") << "</a>";
                }
                cform << "</label></div>";
                cform << "<input type=\"submit\" value=\"" << tr(_lang,"Send") << "\"></form></div>";
            }else if(validMail && !name.empty() && !message.empty() && !sessionid.empty() && !consent){
                cform << "<div id=\"contactform\" ><span class=\"error\">" << tr(_lang,"Please accept the privacy policy to send your message.") << "</span>";
                cform << "<form method=\"post\" action=\"" << Args->config->buildurl("contact")
                <<"\" ><label for=\"name\">" << tr(_lang,"Name:") << " </label> <input type=\"text\" id=\"name\" name=\"name\" value=\"" << name << "\" >"
                << "<label for=\"mail\">" << tr(_lang,"Email:") << "</label>"
                << " <input id=\"mail\" name=\"mail\" type=\"email\" value=\"" << mail << "\" ><br><label for=\"message\">" << tr(_lang,"Message:") << "</label><textarea id=\"message\""
                << " name=\"message\" >" << message << "</textarea>";
                cform << "<div class=\"privacy-consent\"><input type=\"checkbox\" id=\"privacy_consent\" name=\"privacy_consent\" required>";
                cform << "<label for=\"privacy_consent\">" << tr(_lang,"I agree that my data will be processed to handle my inquiry.");
                if(!_PrivacyUrl.empty()){
                    cform << " <a href=\"" << _PrivacyUrl << "\" target=\"_blank\">" << tr(_lang,"Privacy Policy") << "</a>";
                }
                cform << "</label></div>";
                cform << "<input type=\"submit\" value=\"" << tr(_lang,"Send") << "\"></form></div>";
            }else{
                cform << "<div id=\"contactform\" ><form method=\"post\" action=\"" << Args->config->buildurl("contact")
                <<"\" ><label for=\"name\">" << tr(_lang,"Name:") << " </label> <input type=\"text\" id=\"name\" name=\"name\" ><label for=\"mail\">" << tr(_lang,"Email:") << "</label>"
                << " <input id=\"mail\" name=\"mail\" type=\"email\"><br><label for=\"message\">" << tr(_lang,"Message:") << "</label><textarea id=\"message\""
                << " name=\"message\" ></textarea>";
                cform << "<div class=\"privacy-consent\"><input type=\"checkbox\" id=\"privacy_consent\" name=\"privacy_consent\" required>";
                cform << "<label for=\"privacy_consent\">" << tr(_lang,"I agree that my data will be processed to handle my inquiry.");
                if(!_PrivacyUrl.empty()){
                    cform << " <a href=\"" << _PrivacyUrl << "\" target=\"_blank\">" << tr(_lang,"Privacy Policy") << "</a>";
                }
                cform << "</label></div>";
                cform << "<input type=\"submit\" value=\"" << tr(_lang,"Send") << "\"></form></div>";

            }

            libhtmlpp::HtmlElement *hmain;
            if( (hmain=page->getElementbyID("main")) )
                    hmain->appendChild(cform.parse());

            Args->theme->printSite(tid,out,page,req.getRequestURL(),Args->auth->isLoggedIn(tid,req,sessionid));

            libhttppp::HttpResponse curres;
            curres.setContentType("text/html");
            curres.setState(HTTP200);
            sendCompressed(req, curres, out.c_str(),out.length());
            return true;

        };

        bool handleVerify(const int tid, libhttppp::HttpRequest &req, libhtmlpp::HtmlElement *page, const std::string &sessionid){
            libhttppp::HttpForm form;
            form.parse(req);

            std::string token;
            for(const auto &cur : form.urlData()){
                if(cur.key == "token" && !cur.value.empty()){
                    token = cur.value;
                }
            }

            libhtmlpp::HtmlString cform, out;
            std::string _lang = getLang(*Args->database[tid]);

            static const std::regex uuidRegex(R"(^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)");

            if(!token.empty() && std::regex_match(token, uuidRegex)){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> sbuf;

                sql << "SELECT name,mail,message,created_at FROM contact_pending WHERE token='"
                    << sql.escaped(sbuf, token.c_str()) << "'";
                int rows = Args->database[tid]->exec(sql, res);

                if(rows > 0){
                    std::string name = res[0][0];
                    std::string mail = res[0][1];
                    std::string message = res[0][2];
                    std::string createdAt = res[0][3];

                    auto now = std::chrono::system_clock::now();
                    auto epoch = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
                    long long created = std::stoll(createdAt);
                    long long age = epoch - created;

                    if(age <= 86400){
                        std::string subject = "Message from Blogi(";
                        subject += name;
                        subject += ") ";
                        subject += mail;
                        sendMail(Args->smtp.get(), _Receiver.c_str(), subject.c_str(), message);

                        sql.clear();
                        sql << "DELETE FROM contact_pending WHERE token='"
                            << sql.escaped(sbuf, token.c_str()) << "'";
                        Args->database[tid]->exec(sql, res);

                        cform << "<div id=\"contactform\"><span>"
                              << tr(_lang,"Your email has been verified and your message has been sent. Thank you!")
                              << "</span></div>";
                    }else{
                        sql.clear();
                        sql << "DELETE FROM contact_pending WHERE token='"
                            << sql.escaped(sbuf, token.c_str()) << "'";
                        Args->database[tid]->exec(sql, res);

                        cform << "<div id=\"contactform\"><span class=\"error\">"
                              << tr(_lang,"This verification link has expired. Please submit the contact form again.")
                              << "</span></div>";
                    }
                }else{
                    cform << "<div id=\"contactform\"><span class=\"error\">"
                          << tr(_lang,"Invalid or already used verification link.")
                          << "</span></div>";
                }
            }else{
                cform << "<div id=\"contactform\"><span class=\"error\">"
                      << tr(_lang,"Invalid verification link.")
                      << "</span></div>";
            }

            libhtmlpp::HtmlElement *hmain;
            if((hmain = page->getElementbyID("main")))
                hmain->appendChild(cform.parse());

            Args->theme->printSite(tid, out, page, req.getRequestURL(), Args->auth->isLoggedIn(tid, req, sessionid));

            libhttppp::HttpResponse curres;
            curres.setContentType("text/html");
            curres.setState(HTTP200);
            sendCompressed(req, curres, out.c_str(), out.length());
            return true;
        }

        const std::string getName(){
            return "contact";
        }

        const std::string getVersion(){
            return "0.2alpha";
        }

        const std::string getAuthor(){
            return "Jan Koester";
        }
    private:
        std::string _Receiver;
        std::string _PrivacyUrl;
    };
};

EXPORT blogi::PluginApi* create() {
    return new blogi::Contact();
}

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