#include <saas/deploy_manager/scripts/generic.h>
#include <saas/deploy_manager/server/client/client.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/scripts/common/scripts_helper.h>
#include <saas/deploy_manager/meta/cluster.h>

#include <util/generic/algorithm.h>

namespace {
    struct TSlotsExtractor: public NSearchMapParser::ISearchMapScannerCallback {
        struct TElement {
            TString Service;
            TString ConfigType;
            TString Slot;
        };

        const TVector<TString> Servers;
        TVector<TElement> Result;

        TSlotsExtractor(const TVector<TString>& servers)
            : Servers(servers)
        {}

        bool OnService(const NSearchMapParser::TSearchMapService& /*info*/) override {
            return true;
        }

        void OnHost(const NSearchMapParser::TSearchMapHost& host, const NSearchMapParser::TSearchMapReplica& replica, const NSearchMapParser::TSearchMapService& service) override {
            if (Find(Servers.begin(), Servers.end(), host.Name) != Servers.end()) {
                TElement e = {
                    service.Name,
                    replica.Alias,
                    host.GetSlotName()
                };
                Result.push_back(e);
            }
        }
    };
}

class TReleaseServersCommand: public TGenericScriptOnlyCommand<TReleaseServersCommand> {
protected:
    TArgument<TString> Ctype;
    TArgument<TString> ServiceType;
    TArgument<TString> Servers;

public:
    TReleaseServersCommand()
        : Ctype(*this, "ctype")
        , ServiceType(*this, "service_type")
        , Servers(*this, "servers")
    {}

    TReleaseServersCommand(const TString& servers, const TString& ctype, const TString& serviceType)
        : TReleaseServersCommand()
    {
        Servers = servers;
        Ctype = ctype;
        ServiceType = serviceType;
    }

    static TString GetNameStatic() {
        return "ReleaseServers";
    }

    bool Process(IDeployInfoRequest& request) override {
        const auto& cgi = request.GetRD().CgiParam;
        ParseCgi(cgi);

        if (ServiceType != RTYSERVER_SERVICE) {
            ythrow yexception() << "service type " << ServiceType.ToString() << " is not supported";
        }

        auto cluster = NSaas::TClusterLocker::DefineMutable(request, Ctype);
        auto servers = SplitString(Servers, ",");

        TSlotsExtractor extractor(servers);
        NSaas::TCluster::TSearchMaps sm;
        cluster->BuildSearchMaps(sm, false);
        sm.RTY.Scan(extractor);

        for (auto&& e : extractor.Result) {
            auto service = cluster->ServiceSearchMap(e.Service);
            if (!service) {
                ythrow yexception() << "service disappeared";
            }
            auto replica = service->GetReplica(e.ConfigType);
            if (!replica) {
                ythrow yexception() << "configType disappeared";
            }

            auto& mutableSlots = replica->GetMutableSlots();
            for (size_t i = 0; i < mutableSlots.size();) {
                if (mutableSlots[i].GetSlotName() == e.Slot) {
                    mutableSlots.erase(mutableSlots.begin() + i);
                } else {
                    ++i;
                }
            }
        }

        TString errorMessage;
        if (!cluster->Validate(errorMessage)) {
            DEBUG_LOG << "Can't store cluster meta: " << errorMessage << Endl;
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Incorrect new cluster: " << errorMessage;
            return false;
        }

        if (!cluster.Save()) {
            request.Output() << "HTTP/1.1 500 \r\n\r\n";
            request.Output() << "Incorrect storage structure";
            return false;
        }
        request.Output() << "HTTP/1.1 200 \r\n\r\n";
        request.Output() << cluster.GetContent();

        return true;
    }
};

TReleaseServersCommand::TRegistrator RegistratorReleaseServers;
