
#include <kubecontrol/PodController.hpp>
#include "curlpp/Options.hpp"
#include "kubecontrol/Container.hpp"
#include "kubecontrol/Utils.hpp"
#include "nlohmann/json_fwd.hpp"
#include "yaml-cpp/binary.h"
#include <cctype>
#include <exception>
#include <fstream>
#include <future>
#include <iterator>
#include <kubecontrol/KubePod.hpp>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <thread>
#include <filesystem>
#include "loguru.hpp"
#include "yaml-cpp/node/node.h"



namespace kubecontrol
{




    KubePod::KubePod(const std::string &Owner, const  std::string &Uuid, const  std::string &ContainerImage, const std::string &Namespace)
    :Owner_(Utils::to_lower(Owner)),
    Uuid_(Utils::to_lower(Uuid)),
    ContainerRegistry_("kmaster.ti.unibw-hamburg.de:30808"),
    Namespace_(Namespace)
    {


        // EnvirmonentVars_ = std::map<std::string, std::string>();

        this->PathToYaml_ = "config/pods/" + this->Uuid_ + ".yaml";

        if( !std::filesystem::directory_entry("config").exists())
        {
            std::filesystem::create_directory("config");
        }
        std::filesystem::directory_entry entry("config/pods");
        if(!entry.exists())
        {
            std::filesystem::create_directory("config/pods");
        }

        auto map = std::map<std::string, std::string>();
        addContainer(Uuid, ContainerImage,map);
    }


    KubePod::KubePod(const std::string &Owner, const std::string &Uuid, const  std::string &Component, const std::string &ContainerImage, const std::string &Namespace):
    Owner_(Utils::to_lower(Owner)),
    Uuid_(Utils::to_lower(Uuid)),
    Component_(Utils::to_lower(Component)),
    ContainerRegistry_("kmaster.ti.unibw-hamburg.de:30808"),
    Namespace_(Namespace)
    {


        // EnvirmonentVars_ = std::map<std::string, std::string>();

        this->PathToYaml_ = "config/pods/" + this->Uuid_ + ".yaml";

        if( !std::filesystem::directory_entry("config").exists())
        {
            std::filesystem::create_directory("config");
        }
        std::filesystem::directory_entry entry("config/pods");
        if(!entry.exists())
        {
            std::filesystem::create_directory("config/pods");
        }
        auto map = std::map<std::string, std::string>();

        addContainer(Uuid, ContainerImage,map);

    }



     void KubePod::addContainer(std::string name,std::string image, std::map<std::string, std::string> &envs, PullPolicy pullPolicy )
     {

        Container container(Owner_,name,image,pullPolicy);
        container.addEnv(envs);

        ContainerImages_.push_back(container);
     }

    void KubePod::addContainer(Container containter)
    {
        ContainerImages_.push_back(containter);
    }


    void KubePod::addEnvValuesToContainer(std::string containerUUID, std::map<std::string, std::string> envs)
    {
        for (auto container: ContainerImages_)
        {
            if (container.getUUID() == containerUUID +"-container")
            {
                container.addEnv(envs);
            }
        }
    }





    std::string KubePod::getUUID()
    {
        return this->Uuid_;
    }

    std::string KubePod::getOwner()
    {
        return this->Owner_;
    }

    std::string KubePod::getIp()
    {
        return  this->Ip_;
    }
    std::string KubePod::getStatus()
    {
        return  this->Status_;
    }

    void  KubePod::setEnvironmentVar(std::string key, std::string val)
    {
        EnvirmonentVars_.emplace(key,val);
    }

    std::map<std::string, std::string> KubePod::GetEnvironmentVars()
    {
        return EnvirmonentVars_;
    }

    std::string KubePod::GetEnvironmentVar(std::string key)
    {
         if(EnvirmonentVars_.contains(key)) {
            return EnvirmonentVars_[key];
        }
        return "";

    }

    void KubePod::setArgs(std::string &args)
    {
        Args_.emplace_back(Utils::to_lower(args));
    }

    std::vector<std::string> KubePod::GetArgs()
    {
        return Args_;
    }

    void KubePod::setCommand(std::string &command)
    {
        PodCommand_ = Utils::to_lower(command);
    }

    std::string KubePod::getCommand()
    {
        return PodCommand_;
    }

    void KubePod::setComponent(std::string &component)
    {
        this->Component_ = Utils::to_lower(component);
    }

    std::string KubePod::getComponent()
    {
        return this->Component_;
    }

    void KubePod::setName(std::string &name)
    {
        this->Name_ = name;
    }



    std::string KubePod::getName()
    {
        return this->Name_;
    }


    std::string  KubePod::createYAML()
    {
        YAML::Node node;

        node["apiVersion"] = "v1";
        node["kind"] = "Pod";
        node["metadata"]["name"] = this->Uuid_;
        node["metadata"]["namespace"] = this->Namespace_;
        node["metadata"]["labels"]["app.kubernetes.io/name"] = this->Uuid_;

        node["metadata"]["labels"]["app.kubernetes.io/part-of"] =  this->Owner_;

        // if (!std::empty(this->Component_)) node["metadata"]["labels"]["app.kubernetes.io/component"] =  this->Component_;
        node["metadata"]["labels"]["app.kubernetes.io/component"] =  this->Component_;


            for (int i = 0; i< this->ContainerImages_.size(); i++)
            {
                node["spec"]["containers"][i] = this->ContainerImages_[i].toYAML();
                // node["spec"]["containers"][i]["name"] = this->Uuid_+"-container";
                // node["spec"]["containers"][i]["image"] = this->ContainerRegistry_ + "/" + this->ContainerImages_[i];
                // node["spec"]["containers"][i]["imagePullPolicy"] = imagePullPolicy;
                // node["spec"]["containers"][i]["ports"][0]["containerPort"] = 10000;
                // node["spec"]["containers"][i]["ports"][0]["protocol"] = "UDP";


            }




        int i = 0;
        for (const auto& item :EnvirmonentVars_) {
            node["spec"]["containers"][0]["env"][i]["name"] = item.first;
            node["spec"]["containers"][0]["env"][i]["value"] = item.second;
            i++;
        }


        if (!Args_.empty())
        {
            node["spec"]["containers"][0]["args"].SetStyle(YAML::EmitterStyle::Flow);
            for (const auto& item : Args_) {
                node["spec"]["containers"][0]["args"].push_back(item);
            }
        }

        if (!PodCommand_.empty())
        {

            node["spec"]["containers"][0]["command"].SetStyle(YAML::EmitterStyle::Flow);
            node["spec"]["containers"][0]["command"].push_back(PodCommand_);
        }

        node["spec"]["terminationGracePeriodSeconds"] = 5;

        node["spec"]["restartPolicy"] = "Never";

        YAMLNode_ = node;

        std::ofstream fout(this->PathToYaml_);
        fout << node;

        fout.close();

        return this->PathToYaml_;

    }



   int KubePod::start(KubernetesAPI APIInterface,bool WaitTillRunning)
    {
        std::string request = "/api/v1/namespaces/simulator/pods/";

        this->createYAML();

        std::stringstream stream;
        stream << YAMLNode_;
        std::string response = APIInterface.performRequest(request,"POST",stream.str());
        extractInformationFromResopnse(response);

        auto timeoutTime = std::chrono::system_clock::now() + std::chrono::seconds(10);


        if (WaitTillRunning ) {
            while ((this->Status_ != "Running" && this->Status_ != "Succeeded") || std::chrono::system_clock::now() >= timeoutTime) {
            // LOG_S(INFO)<<"wainting till running " << this->Status_;

                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                 this->updateInfoForThisPod(APIInterface);

            }
        }
        else {
            std::this_thread::sleep_for(std::chrono::milliseconds(200));

        }

        return 0;
    }



    int KubePod::stop(KubernetesAPI APIInterface)
    {
        std::string request = "/api/v1/namespaces/simulator/pods/"+Uuid_;
        std::string result = APIInterface.performRequest(request,"DELETE");

        LOG_S(INFO)<<"stopping: " <<Uuid_;

        return 0;

    }


   int KubePod::stopChilds(KubernetesAPI APIInterface)
    {
        auto uuids = this->getPodsChilds(APIInterface);
        if (uuids.empty())
        {
            return 1;
        }
        for(const auto& uuid: uuids)
        {
            std::string request = "/api/v1/namespaces/simulator/pods/"+uuid.UUID;
            std::string result = APIInterface.performRequest(request,"DELETE");

        }

        return 0;


    }


    int KubePod::updateInfoForThisPod(KubernetesAPI APIInterface)
    {
        std::string request = "/api/v1/namespaces/simulator/pods/"+Uuid_+"/status";
        std::string result = APIInterface.performRequest(request,"GET");
        if (result.empty())
        {
            return 1;
        }

        if (extractInformationFromResopnse(result)!= 0)
        {
            return 1;
        }
        return 0;
    }


    std::vector<PodChild> KubePod::getPodsChilds( KubernetesAPI APIInterface)
    {
        std::string request = "/api/v1/namespaces/simulator/pods?labelSelector=app.kubernetes.io/part-of="+Uuid_;
        std::string result = APIInterface.performRequest(request,"GET");

        if (result.empty())
        {
            return  {};
        }

        extractInformationFromResopnse(result);
        return this->ChildPods;
    }



    int  KubePod::extractInformationFromResopnse(std::string response)
    {
        if (response.empty())
        {
            return 1 ;
        }
        // LOG_S(INFO)<< response;
         nlohmann::json j;
         try {
            j = nlohmann::json::parse(response);
            if(j.contains("kind") && j["kind"] == "Pod")
            {
                    if( j["status"].contains("podIP")) this->Ip_ =  j["status"]["podIP"].get<std::string>();
                    this->Status_ = j["status"]["phase"].get<std::string>();
                    this->PartOf_ = j["metadata"]["labels"]["app.kubernetes.io/part-of"];


            }else if(j.contains("items") && j["items"].is_array())
            {

                    for (auto i  : j["items"])
                    {
                        // LOG_S(INFO)<<i;
                        PodChild child;
                        child.UUID =i["metadata"]["name"].get<std::string>();
                        child.IP = i["status"]["podIP"].get<std::string>();
                        child.Status = i["status"]["phase"].get<std::string>();
                        child.Component = i["metadata"]["labels"]["app.kubernetes.io/component"].get<std::string>();
                        this->ChildPods.push_back(child);
                    }

            }else{
                return 1;
            }
        } catch (const std::exception &e) {
            LOG_S(ERROR)<< "Exeption in extractInformationFromResopnse() :" << e.what();
              std::exception_ptr p = std::current_exception();
            LOG_S(ERROR)<< "Exeption :" << p.__cxa_exception_type()->name();
            LOG_S(INFO)<<j;
            return 1;

        }
        return 0;

    }










}
