/*******************************************************************************
 * Copyright (c) 2023, 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 <cstring>
#include <string>
#include <vector>
#include <chrono>
#include <thread>

#include <netplus/socket.h>
#include <netplus/exception.h>

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

#include <dbpp/database.h>

#include <json-c/json.h>
#include <json-c/json_object.h>

#include "plugin.h"
#include "theme.h"
#include "conf.h"
#include <i18n.h>
#include <compress.h>

#define CHANNELID UC9WqtMjJL6VHuDqq9endmEA

#include <iostream>

const std::string GOOGLECA("\
-----BEGIN CERTIFICATE-----\n\
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw\n\
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n\
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n\
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n\
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA\n\
A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo\n\
27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w\n\
Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw\n\
TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl\n\
qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH\n\
szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8\n\
Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk\n\
MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92\n\
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p\n\
aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN\n\
VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID\n\
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\n\
FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb\n\
C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe\n\
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy\n\
h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4\n\
7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J\n\
ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef\n\
MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/\n\
Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT\n\
6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ\n\
0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm\n\
2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb\n\
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c\n\
-----END CERTIFICATE-----\n");

namespace blogi {
    class Youtube : public PluginApi {
    public:
        Youtube(){
        }
        const std::string getName(){
            return "youtube";
        }

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

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

        void initPlugin(){
            registerGPO("f7c23ec1-6753-4367-8816-ab6ccb1f80ff",
                        "Blogi: YouTube Settings",
                        "Controls access to YouTube plugin settings",
                        true);

            dbpp::SQL        sql;
            dbpp::DBResult   res;


            sql << "CREATE TABLE IF NOT EXISTS youtube_channels("
                <<   "id integer PRIMARY KEY " << Args->database[0]->autoincrement(sql) << ","
                <<   "channelname character varying(255) NOT NULL,"
                <<   "apikey character varying(255) NOT NULL,"
                <<   "channelkey character varying(255) NOT NULL"
                << ");"
                << " CREATE TABLE IF NOT EXISTS youtube_videos ("
                <<   "id integer PRIMARY KEY " << Args->database[0]->autoincrement(sql) << ","
                <<   "youtube_channels_pkey bigint REFERENCES youtube_channels(id),"
                <<   "youtube_id varchar(255) NOT NULL"
                << ")";

            Args->database[0]->exec(sql,res);

        }

        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);

            std::string formName, formApiKey, formChannelKey;
            bool hasForm = false;
            std::string delId;
            std::string syncId;

            for(const auto &cur : form.urlData()){
                if(cur.key=="channelname"){
                    formName = cur.value;
                    hasForm = true;
                }else if(cur.key=="apikey"){
                    formApiKey = cur.value;
                    hasForm = true;
                }else if(cur.key=="channelkey"){
                    formChannelKey = cur.value;
                    hasForm = true;
                }else if(cur.key=="delete_id"){
                    delId = cur.value;
                }else if(cur.key=="sync_id"){
                    syncId = cur.value;
                }
            }

            std::string syncMessage;

            if(!syncId.empty()){
                try{
                    libhtmlpp::HtmlString syncOut;
                    SyncYoutube(tid, atoi(syncId.c_str()), syncOut);
                    syncMessage = tr(_lang,"Sync completed!");
                }catch(libhttppp::HTTPException &e){
                    syncMessage = e.what();
                }
                saved = true;
            }

            if(!delId.empty()){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> buf;
                sql << "DELETE FROM youtube_videos WHERE youtube_channels_pkey='" << sql.escaped(buf, delId.c_str()) << "'";
                Args->database[tid]->exec(sql,res);
                sql.clear();
                sql << "DELETE FROM youtube_channels WHERE id='" << sql.escaped(buf, delId.c_str()) << "'";
                Args->database[tid]->exec(sql,res);
                saved = true;
            }

            if(hasForm && !formName.empty() && !formApiKey.empty() && !formChannelKey.empty()){
                dbpp::SQL sql;
                dbpp::DBResult res;
                std::vector<char> buf;
                sql << "INSERT INTO youtube_channels (channelname,apikey,channelkey) VALUES ('"
                    << sql.escaped(buf, formName.c_str()) << "','"
                    << sql.escaped(buf, formApiKey.c_str()) << "','"
                    << sql.escaped(buf, formChannelKey.c_str()) << "')";
                Args->database[tid]->exec(sql,res);
                saved = true;
            }

            json_object *jroot = json_object_new_object();
            json_object_object_add(jroot, "title", json_object_new_string("YouTube"));

            if(saved){
                if(!syncMessage.empty()){
                    json_object_object_add(jroot, "message", json_object_new_string(syncMessage.c_str()));
                }else{
                    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"));
            }

            // Existing channels list
            json_object *jchannels = json_object_new_array();
            {
                dbpp::SQL sql;
                dbpp::DBResult res;
                sql << "SELECT id,channelname,apikey,channelkey FROM youtube_channels";
                int count = Args->database[tid]->exec(sql,res);
                for(int i = 0; i < count; ++i){
                    json_object *jc = json_object_new_object();
                    json_object_object_add(jc, "id", json_object_new_string(res[i][0]));
                    json_object_object_add(jc, "channelname", json_object_new_string(res[i][1]));
                    json_object_object_add(jc, "apikey", json_object_new_string(res[i][2]));
                    json_object_object_add(jc, "channelkey", json_object_new_string(res[i][3]));
                    json_object_array_add(jchannels, jc);
                }
            }
            json_object_object_add(jroot, "items", jchannels);

            // Existing videos list
            json_object *jvideos = json_object_new_array();
            {
                dbpp::SQL sql;
                dbpp::DBResult res;
                sql << "SELECT v.id,COALESCE(c.channelname,'unknown'),v.youtube_id FROM youtube_videos v "
                    << "LEFT JOIN youtube_channels c ON v.youtube_channels_pkey=c.id";
                int count = Args->database[tid]->exec(sql,res);
                for(int i = 0; i < count; ++i){
                    json_object *jv = json_object_new_object();
                    json_object_object_add(jv, "id", json_object_new_string(res[i][0]));
                    json_object_object_add(jv, "channelname", json_object_new_string(res[i][1]));
                    json_object_object_add(jv, "youtube_id", json_object_new_string(res[i][2]));
                    json_object_array_add(jvideos, jv);
                }
            }
            json_object_object_add(jroot, "videos", jvideos);

            // Add channel form
            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/youtube").c_str()));

            json_object *jfields = json_object_new_array();

            auto addField = [&](const char *name, const char *label, const char *type, const std::string &value){
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string(name));
                json_object_object_add(jf, "label", json_object_new_string(label));
                json_object_object_add(jf, "type", json_object_new_string(type));
                json_object_object_add(jf, "value", json_object_new_string(value.c_str()));
                json_object_array_add(jfields, jf);
            };

            addField("channelname", "Channel Name", "text", "");
            addField("apikey", "API Key", "text", "");
            addField("channelkey", "Channel ID", "text", "");

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

            // Delete form
            json_object *jdelform = json_object_new_object();
            json_object_object_add(jdelform, "method", json_object_new_string("POST"));
            json_object_object_add(jdelform, "action", json_object_new_string(
                Args->config->buildurl("settings/youtube").c_str()));
            json_object *jdelfields = json_object_new_array();
            {
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string("delete_id"));
                json_object_object_add(jf, "label", json_object_new_string("Channel ID to delete"));
                json_object_object_add(jf, "type", json_object_new_string("text"));
                json_object_object_add(jf, "value", json_object_new_string(""));
                json_object_array_add(jdelfields, jf);
            }
            json_object_object_add(jdelform, "fields", jdelfields);
            json_object_object_add(jdelform, "submit", json_object_new_string(tr(_lang,"Delete Channel").c_str()));
            json_object_object_add(jroot, "delete_form", jdelform);

            // Sync form
            json_object *jsyncform = json_object_new_object();
            json_object_object_add(jsyncform, "method", json_object_new_string("POST"));
            json_object_object_add(jsyncform, "action", json_object_new_string(
                Args->config->buildurl("settings/youtube").c_str()));
            json_object *jsyncfields = json_object_new_array();
            {
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string("sync_id"));
                json_object_object_add(jf, "label", json_object_new_string("Channel ID to sync"));
                json_object_object_add(jf, "type", json_object_new_string("text"));
                json_object_object_add(jf, "value", json_object_new_string(""));
                json_object_array_add(jsyncfields, jf);
            }
            json_object_object_add(jsyncform, "fields", jsyncfields);
            json_object_object_add(jsyncform, "submit", json_object_new_string(tr(_lang,"Sync Videos").c_str()));
            json_object_object_add(jroot, "sync_form", jsyncform);

            return jroot;
        }

        void SyncYoutube(const int tid,int channel_id,libhtmlpp::HtmlString &out){
            std::string _lang = getLang(*Args->database[tid]);
            std::vector<char> json;
            libhttppp::HttpResponse res;
            size_t hsize=0;
            int cpos=0;
            bool chunked=false;
            dbpp::SQL        sql;
            dbpp::DBResult   dbres;

            int rlen=0;

            sql << "select channelname,apikey,channelkey from youtube_channels where id='" << channel_id <<"'";

            int count = Args->database[tid]->exec(sql,dbres);

            if(count < 1){
                libhttppp::HTTPException he;
                he[libhttppp::HTTPException::Error] << "Youtube channel id not found in database !";
                throw he;
            }

            try{
                netplus::x509cert ycert;
                ycert.loadFromBuffer(ycert.pemCertToDer(GOOGLECA));
                libhttppp::HttpUrl yurl("https://www.googleapis.com");
                libhttppp::HttpClient clt(yurl);
             //   ycsock->setTimeout(10);

                libhttppp::HttpRequest nreq;
                nreq.setRequestType(GETREQUEST);
                std::string churl="/youtube/v3/search?key="; churl+= dbres[0][1];
                churl+="&channelId="; churl+=dbres[0][2]; churl+="&part=snippet,id&order=date";
                nreq.setRequestURL(churl.c_str());
                nreq.setRequestVersion(HTTPVERSION(1.1));
                nreq.setHeaderData("connection")->push_back("keep-alive");
                nreq.setHeaderData("host")->push_back("www.googleapis.com:443");
                nreq.setHeaderData("accept")->push_back("text/json");
                nreq.setHeaderData("user-agent")->push_back("blogi/1.0 (Alpha Version 0.1)");
                json=clt.Get(nreq);
            }catch(netplus::NetException &e){
                libhttppp::HTTPException ee;
                ee[libhttppp::HTTPException::Error] << e.what();
                throw ee;
            }

            json.push_back('\0');

            libhtmlpp::HtmlElement youmain("ul");

            struct json_object *youindex;
            youindex = json_tokener_parse(json.data());

            if(!youindex){
                libhttppp::HTTPException e;
                e[libhttppp::HTTPException::Error] << "nginxfiler: counld't read json !";
                throw e;
            }

            try{
                struct json_object *ytems;
                ytems = json_object_object_get(youindex,"items");
                int fcount = json_object_array_length(ytems);

                sql.clear();
                sql << "delete from youtube_videos where youtube_channels_pkey='" << channel_id <<"'";
                Args->database[tid]->exec(sql,dbres);

                for(int i =0; i<fcount; ++i) {
                    json_object_object_foreach(json_object_array_get_idx(ytems,i), key, val) {
                        if(strcmp(key,"id")==0 && val){
                            struct json_object *yid;
                            yid = json_object_object_get(val,"videoId");
                            if(yid){
                                sql.clear();
                                sql << "INSERT INTO youtube_videos (youtube_channels_pkey,youtube_id) VALUES ('" << channel_id << "','" << json_object_get_string(yid) << "')";
                                Args->database[tid]->exec(sql,dbres);
                            }
                        }
                    }
                }
                out << "<span>" << fcount << " "+tr(_lang,"videos added!")+"" << "</span>";
                return;
            }catch(libhtmlpp::HTMLException &e){
                libhttppp::HTTPException ee;
                ee[libhttppp::HTTPException::Error] << e.what();
                throw ee;
            }
        }

        bool Controller(const int tid,libhttppp::HttpRequest & req,libhtmlpp::HtmlElement *page,const std::string &sessionid){
            std::string _lang = getLang(*Args->database[tid]);
            libhtmlpp::HtmlString out;
            libhtmlpp::HtmlElement *hmain, youdiv("div");

            youdiv.setAttribute("id","youtube-pl");

            if(req.getRequestURL().compare(Args->config->buildurl("youtube"))!=0){
                return false;
            }

            if(req.getRequestURL().compare(Args->config->buildurl("youtube/sync/"))>0){
                if(Args->auth->isLoggedIn(tid,req,sessionid)){
                    try{
                        size_t surl=Args->config->buildurl("youtube/sync/").length();
                        SyncYoutube(tid,atoi( req.getRequestURL().substr(surl,req.getRequestURL().length()-surl).c_str()),out);
                        youdiv.appendChild(out.parse());
                    }catch(libhttppp::HTTPException &e){
                        std::string error;
                        libhtmlpp::HtmlString msg;
                        libhtmlpp::HtmlEncode(e.what(),error);
                        msg << "<span>" << error << "</span>";
                        youdiv.appendChild(msg.parse());
                    }
                }else{
                        libhtmlpp::HtmlString msg;
                        msg << "<span>"+tr(_lang,"Insufficient rights to sync YouTube videos!")+"</span>";
                        youdiv.appendChild(msg.parse());
                }
                if( (hmain=page->getElementbyID("main")) )
                        hmain->appendChild(&youdiv);
                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;

            }
            dbpp::SQL        sql;
            dbpp::DBResult   dbres;

            sql << "SELECT youtube_channels.channelname,youtube_videos.youtube_id "
                << "FROM youtube_channels LEFT JOIN youtube_videos ON youtube_channels.id = youtube_videos.youtube_channels_pkey;";

            int count = Args->database[tid]->exec(sql,dbres);

            if(count < 1){
                libhttppp::HTTPException ee;
                ee[libhttppp::HTTPException::Error] << "no youtube channels or videos found please sync!";
                throw ee;
            }

            libhtmlpp::HtmlElement youul("ul");

            libhtmlpp::HtmlString head;

            head << "<span class=\"youtiem_head\">" << dbres[0][0] << "</span>";

            youdiv.appendChild(head.parse());

            for(int i=0; i<count; ++i){
                libhtmlpp::HtmlString youmain;
                if(req.isMobile())
                    youmain << "<li><iframe class=\"ytplayer\" width=\"320\" height=\"240\" src=\"https://www.youtube.com/embed/"
                            << dbres[i][1]  << "?autoplay=1&hd=1&origin=" << Args->config->getsiteurl() << "\" frameborder=\"0\" allowFullscreen> </iframe></li>";
                else
                    youmain << "<li><iframe class=\"ytplayer\" width=\"640\" height=\"360\" src=\"https://www.youtube.com/embed/"
                            << dbres[i][1]  << "?autoplay=1&hd=1&origin=" << Args->config->getsiteurl() << "\" frameborder=\"0\" allowFullscreen> </iframe></li>";
                youul.appendChild(youmain.parse());
            }

            youdiv.appendChild(&youul);

            if( (hmain=page->getElementbyID("main")) )
                hmain->appendChild(&youdiv);

            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;
        }
    private:
        int readchunk(const char *data,int datasize,int &pos){
            int start=pos;

            char value[512];

            while( (pos < datasize) && data[++pos]!='\r'){};

            int len=pos-start;

            if(len > 512){
                libhttppp::HTTPException ee;
                ee[libhttppp::HTTPException::Error] << "nginxfiler: chunck size: " << len << " to big aborting !";
                throw ee;
            }

            memcpy(value,data+start,len);

            value[len]='\0';

            if (len < 1) {
                return 0;
            }

            int result=strtol(value, NULL, 16);

            pos+=2;

            return result;
        }
    };
};

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

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