#include "deploy_builder.h"
#include <saas/deploy_manager/scripts/common/file_content_generators/description.h>
#include <saas/deploy_manager/scripts/common/slots_allocator/allocator.h>
#include <saas/deploy_manager/modules/resources/resources.h>

namespace NRTYDeployInfo {

    bool IDeployServiceInfo::BuildNodesCurrent(TStringStream& errors, NRTYDeploy::TNodesStructure* nodesExt, i64& version) {
        NodesCurrent.clear();
        TString currConf;
        TString pathConf = GetCurrentConfigsPath();
        NRTYDeploy::TNodesStructure* nodes = &NodesCurrent;
        if (nodesExt)
            nodes = nodesExt;
        if (version == -1) {
            ComponentInfo->GetCommon()->GetStorage().GetVersion(pathConf, version);
        }
        if (ComponentInfo->GetCommon()->GetStorage().GetValue(pathConf, currConf, version)) {
            if (!nodes->Deserialize(currConf, &version)) {
                errors << "can't parse current conf. no_revert must be true";
                return false;
            }
        } else {
            errors << "can't find prev configuration. no_revert must be true";
            return false;
        }
        return true;
    }

    const NRTYDeploy::ICommonData* IDeployServiceInfo::GetCommon() const {
        return ComponentInfo->GetCommon();
    }

    TString IDeployServiceInfo::GetCType() const {
        return ComponentInfo->GetCType();
    }

    TString IDeployServiceInfo::GetCurrentConfigsPath() const {
        return "/configs/" + Service + "/" + ComponentInfo->GetCType() + "/" + ComponentInfo->GetComponentName() + "_" + CURRENT_CONFIGS_FILE;
    }

    TVector<NSearchMapParser::TSearchMapHost> IDeployServiceInfo::GetUsedSlots() const {
        NSearchMapParser::TSearchMap ism = ComponentInfo->SearchMap(Service);
        return ism.GetSlots();
    }

    void IDeployServiceInfo::SetInfo(IDeployComponentInfo* componentInfo, const TString& service) {
        Service = service;
        ComponentInfo = componentInfo;
    }

    bool IDeployServiceInfo::DoBuildNodesCurrent(const TString& currConf, NRTYDeploy::TNodesStructure& nodes) {
        return nodes.Deserialize(currConf);
    }

    bool IDeployServiceInfo::DoBuildFilesInfo(const NRTYDeploy::TFileContentGenerator::TContext& context, TStringStream& errors, bool /*forcedOnly*/) {
        if (!NRTYDeploy::TFileContentGenerator::AddGenerators(Nodes, *GetCommon(), GetConfigsPath(), "", context.SlotInfo)) {
            ERROR_LOG << "Can't build files list for " << GetConfigsPath() << Endl;
            errors << "Incorrect resource " << GetConfigsPath() << Endl;
            return false;
        }

        NSaas::TSlotInfo slotInfo;
        slotInfo.CType = GetCType();
        slotInfo.ServiceType = ComponentInfo->GetComponentName();
        slotInfo.Service = context.SlotInfo.Service;
        if (NSlotNameUtil::IsNormalDC(context.SlotInfo.DC))
            slotInfo.DC = context.SlotInfo.DC;

        NRTYDeploy::TFileContentGenerator::AddGenerators(Nodes, *GetCommon(), "common/" + GetCType(), "environment", slotInfo);
        Nodes.push_back(new NRTYDeploy::TDescriptionFileContentGenerator());
        return true;
    }

    bool IDeployServiceInfo::BuildFilesInfo(const NRTYDeploy::TFileContentGenerator::TContext& context, TStringStream& errors, const TString& version) {
        i64 versionInt = -1;
        Nodes.clear();
        try {
            if (version == "NEW") {
                return DoBuildFilesInfo(context, errors, false);
            } else if (version == "CURRENT") {
                NRTYDeploy::TNodesStructure structLoc;
                if (!BuildNodesCurrent(errors, &structLoc, versionInt)) {
                    return DoBuildFilesInfo(context, errors, false);
                }
                if (context.ForceServices.size()) {
                    if (!DoBuildFilesInfo(context, errors, true)) {
                        ERROR_LOG << "Can't build new files for forcing" << Endl;
                        return false;
                    } else {
                        for (auto&& i : Nodes) {
                            for (auto&& force : context.ForceStructures) {
                                for (auto&& servFile : force.second) {
                                    if (TFsPath(i->GetRename(context)).Fix().GetName() == TFsPath(servFile->GetRename(context)).Fix().GetName())
                                        structLoc.UpdateByRename(i, context);
                                }
                            }
                            for (auto&& force : context.ForceServices) {
                                if (TFsPath(i->GetRename(context)).Fix().GetName() == force)
                                    structLoc.UpdateByRename(i, context);
                            }
                        }
                    }
                }
                Nodes = structLoc;
                return true;
            } else {
                versionInt = FromString<i64>(version);
                return BuildNodesCurrent(errors, &Nodes, versionInt);
            }
        } catch (...) {
            errors << "Can't build " << version << " files info for " << context.SlotInfo.ServiceType << " " << context.SlotInfo.Service << ": " << CurrentExceptionMessage() << Endl;
            ERROR_LOG << "Can't build " << version << " files info for " << context.SlotInfo.ServiceType << " " << context.SlotInfo.Service << ": " << CurrentExceptionMessage() << Endl;
            return false;
        }
        FAIL_LOG("Undef behaviour");
    }

    void IDeployComponentInfo::BuildFakeSearchMap(const NRTYCluster::TCTypeCluster& cluster, const TString& serviceName, NSearchMapParser::TSearchMap& result) {
        NSearchMapParser::TSearchMapService& sms = result.AddService(serviceName);
        NSearchMapParser::TSearchMapReplica replica;
        replica.Alias = "default";
        TMap<TString, ui32> dcIndex;
        for (const auto& slot : cluster.GetSlots()) {
            if (dcIndex.find(slot.second.GetDC()) == dcIndex.end()) {
                size_t size = dcIndex.size();
                dcIndex[slot.second.GetDC()] = size;
            }
        }
        for (const auto& slot : cluster.GetSlots()) {
            const NRTYCluster::TSlotData& sd = slot.second;
            NSearchMapParser::TSearchMapHost smh;
            const TString dc = sd.GetDC();
            smh.Shards.SetMin(dcIndex[dc] * sms.GetShardsMax() / dcIndex.size());
            if (dcIndex[sd.GetDC()] + 1 != dcIndex.size())
                smh.Shards.SetMax((dcIndex[dc] + 1) * sms.GetShardsMax() / dcIndex.size() - 1);
            else
                smh.Shards.SetMax(sms.GetShardsMax());
            smh.Name = sd.FullHost();
            smh.SearchPort = sd.Port;
            smh.IndexerPort = sd.Port + 2;
            smh.DisableIndexing = false;
            smh.DisableSearch = false;
            if (NSlotNameUtil::IsNormalDC(dc))
                smh.DC = dc;
            replica.Hosts.push_back(smh);
        }
        sms.Replicas.push_back(replica);
    }

    namespace {
        const TString UNUSED_PREFIX("unused");
        const TString UNUSED_AND_USED_PREFIX(UNUSED_PREFIX + "_and_used");
    }

    NSearchMapParser::TSearchMap IDeployComponentInfo::SearchMap(const TString& service, bool unusedOnly) const {
        NSearchMapParser::TSearchMap searchMap;
        auto&& clusterInfo = GetCommon()->GetResourcesManager().GetCluster(GetComponentName(), service);
        const TMap<TString, NRTYCluster::TCTypeCluster>& cluster = clusterInfo.GetCTypeCluster();
        auto iter = cluster.find(CType);
        if (iter != cluster.end()) {
            const NRTYCluster::TCTypeCluster& allSlots = iter->second;
            if (unusedOnly) {
                NRTYCluster::TPoolFilter filter(*this, true);
                NRTYCluster::TCTypeCluster::TPtr filteredSlots = allSlots.FilterSlots(filter);
                BuildFakeSearchMap(*filteredSlots, UNUSED_PREFIX, searchMap);
            } else
                BuildFakeSearchMap(allSlots, service, searchMap);
        }
        searchMap.Compile(false, false);
        return searchMap;
    }

    NSearchMapParser::TSearchMap IDeployComponentInfo::SearchMap(const TString& service) const {
        if (service.StartsWith(UNUSED_PREFIX)) {
            TString realService;
            bool used = service.StartsWith(UNUSED_AND_USED_PREFIX);
            if (used)
                realService = LegacySubstr(service, UNUSED_AND_USED_PREFIX.size() + 1);
            else
                realService = LegacySubstr(service, UNUSED_PREFIX.size() + 1);
            if (!realService)
                realService = "*";
            return SearchMap(realService, !used);
        } else if (service == "*") {
            return SearchMap();
        } else {
            return DoSearchMap(service);
        }
    }

}
