#include "request.h"

#include <saas/deploy_manager/debug/messages.h>
#include <saas/deploy_manager/scripts/common/script.h>
#include <saas/deploy_manager/storage/profile_storage.h>
#include <saas/deploy_manager/unistat_signals/signals.h>

#include <saas/util/external/diff.h>
#include <saas/util/external/mail_service.h>

#include <library/cpp/http/misc/httpcodes.h>

#include <util/generic/hash.h>
#include <util/string/vector.h>

TDeployInfoRequest::TDeployInfoRequest(NRTYDeploy::ICommonData* commonData, const TDeployManagerConfig& config)
    : CommonData(commonData)
    , Config(config)
    , Storage(new NRTYDeploy::TProfileStorage(RC, commonData->GetStorage()))
{}

bool TDeployInfoRequest::Reply(void*) {
    ProcessHeaders();
    RD.Scan();
    if (!!Config.GetRequiredUriPrefix()) {
        TString path{RD.ScriptName()};
        if (!path.StartsWith(Config.GetRequiredUriPrefix())) {
            Output() << "HTTP/1.1 400 \r\n\r\n";
            Output() << "There is no required uri prefix " << Config.GetRequiredUriPrefix() << ": " << path;
            return true;
        }
        path = "/" + path.substr(Config.GetRequiredUriPrefix().length());
        path += "?" + RD.CgiParam.Print();
        RD.Parse(path.data());
        RD.CgiParam.Scan(RD.Query());
    }
    try {
        GetRequestWizardModule().Process(RD, RequestString);
    } catch (const yexception& e) {
        Output() << "HTTP/1.1 400 \r\n\r\n";
        Output() << e.what();
        return true;
    }

    const TString path{RD.ScriptName()};

    if (path.StartsWith("/help")) {
        TSet<TString> keys;
        Singleton<NRTYDeploy::IScript::TFactory>()->GetKeys(keys);
        Output() << "HTTP/1.1 200 \r\n\r\n";
        for (TSet<TString>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
            THolder<NRTYDeploy::IScript> script(NRTYDeploy::IScript::TFactory::Construct(*i));
            Output() << "---------------------------------" << Endl;
            Output() << *i << Endl;
            Output() << script->Help() << Endl;
        }
        Output() << "---------------------------------" << Endl;
        Output() << GetRequestWizardModule().Help() << Endl;

        return true;
    }
    RC.Text = path + "?" + RD.CgiParam.Print();
    RC.Id = ComputeHash(RC.Text + RC.Start.ToString());
    INFO_LOG << "Process query=" << RC.Text << "; id=" << RC.Id << "..." << Endl;
    const TString command = path.substr(1);
    THolder<NRTYDeploy::IScript> script(NRTYDeploy::IScript::TFactory::Construct(command));
    if (!script) {
        Output() << "HTTP/1.1 400 \r\n\r\n";
        Output() << "Incorrect command " << command;
        return true;
    }

    bool permitted = false;
    try {
        AccessControl.Reset(new TUserAccessModule(RD.CgiParam, command, GetStorage(), Config));
        permitted = script->CommandPermitted(*this);
    } catch (const yexception& e) {
        Output() << "HTTP/1.1 401 Unauthorized \r\n\r\n";
        Output() << e.what();
        return true;
    }
    if (RD.CgiParam.Has("check_permissions") && FromString<bool>(RD.CgiParam.Get("check_permissions"))) {
        Output() << "HTTP/1.1 200 Ok\r\n\r\n";
        Output() << "{\"permitted\" :" << (permitted ? "true" : "false") << "}";
        return true;
    }

    if (!permitted) {
        Output() << "HTTP/1.1 403 Forbidden \r\n\r\n";
        Output() << "Operation not permited";
        return true;
    }
    bool result = false;
    TString error = "";
    try {
        TDebugMessageStartRequestProcessing dbgMsg(command, RD, GetBlob());
        SendGlobalMessage(dbgMsg);
        result = script->Process(*this);
    } catch (const NRTYDeploy::TCodedException& e) {
        error = e.what();
        Output() << "HTTP/1.1 " << HttpCodeStrEx(e.GetCode()) << "\r\n\r\n";
        Output() << error;
    } catch (const yexception& e) {
        error = e.what();
        Output() << "HTTP/1.1 " << HttpCodeStrEx(500) << "\r\n\r\n";
        Output() << error;
    }
    TDuration duration = Now() - RC.Start;
    INFO_LOG << "Process query=" << RC.Text << "; id=" << RC.Id << "; result=" << result << "; duration=" << duration.ToString() << "; error=" << error << Endl;
    TSaasDmUnistatSignals::DoUnistatRecord(command, result, duration.MilliSeconds());
    bool diffData = !!script->GetOldData() || !!script->GetNewData();
    bool infoData = !!script->GetAdditionalMessage();
    if (diffData || infoData) {
        const TString& service = RD.CgiParam.Get("service");
        const TString& ctype = RD.CgiParam.Get("ctype");
        NRTY::TMailInfoMessage message(Config.GetNotifyAddress(),
            "COMMAND:\n" + RD.CgiParam.Print() + "\n" + (result ? "" : " FAILED ") +
            (infoData ? "INFO:\n" + script->GetAdditionalMessage() + "\n" : "") +
            (diffData ? "DIFF:" + NRTY::TDiffBuilder::Calc(script->GetOldData(), script->GetNewData()) : ""),
            service + "/" + ctype + "/" + (TString)RD.ScriptName());
        SendGlobalMessage(message);
    }

    return true;
}

NRTYDeploy::IVersionedStorage& TDeployInfoRequest::GetStorage() {
    return *Storage;
}

const NRTYDeploy::IVersionedStorage& TDeployInfoRequest::GetStorage() const {
    return *Storage;
}

const NRTYDeploy::TResourcesManager& TDeployInfoRequest::GetResourcesManager() const {
    return CommonData->GetResourcesManager();
}

NRTYDeploy::IDistrTasksQueue& TDeployInfoRequest::GetQueue() {
    return CommonData->GetQueue();
}

TNannyModule& TDeployInfoRequest::GetNannyModule() const {
    return CommonData->GetNannyModule();
}

TSDModule& TDeployInfoRequest::GetSDModule() const {
    return CommonData->GetSDModule();
}

TJugglerModule& TDeployInfoRequest::GetJugglerModule() const {
    return CommonData->GetJugglerModule();
}

TGolovanModule& TDeployInfoRequest::GetGolovanModule() const {
    return CommonData->GetGolovanModule();
}

const TString& TDeployInfoRequest::GetDeployManagerBalanserHost() const {
    return CommonData->GetDeployManagerBalanserHost();
}

ui16 TDeployInfoRequest::GetDeployManagerBalanserPort() const {
    return CommonData->GetDeployManagerBalanserPort();
}

TRequestWizardModule& TDeployInfoRequest::GetRequestWizardModule() {
    return CommonData->GetRequestWizardModule();
}

NRTYDeploy::IControllersChecker& TDeployInfoRequest::GetControllersChecker() {
    return CommonData->GetControllersChecker();
}

const TString& TDeployInfoRequest::GetDeployManagerBalanserUriPrefix() const {
    return CommonData->GetDeployManagerBalanserUriPrefix();
}

TServices20Module& TDeployInfoRequest::GetServices20Module() {
    return CommonData->GetServices20Module();
}

NRTYDeploy::TCacheModule& TDeployInfoRequest::GetCacheModule() {
    return CommonData->GetCacheModule();
}
void TDeployInfoRequest::SafeAddAndOwn(TAutoPtr<IObjectInQueue> obj) {
    CommonData->SafeAddAndOwn(obj);
}
