#include "misspelledcontext.h"

#include <saas/searchproxy/common/cgi.h>

void TMisspellContext::Fill(const TCgiParameters& cgi) {
    TryFromString(cgi.Get("misspell_full_response"), DumpResponse);
    TryFromString(cgi.Get("msp"), Policy);
    TryFromString(cgi.Get("mis_rel_min"), MinRel);
    OriginalText = cgi.Get("text");
    if (Policy != EPolicy::Skip) {
        const auto& qtree = cgi.Get("qtree");
        if (!qtree.empty()) {
            Policy = EPolicy::Skip;
        }
    }
    Initialized = true;
}

void TMisspelledSearchContext::SetText(const TString& text, TReqEnv* reqEnv) {
    const TString& templated = TemplateCorrector ? TemplateCorrector->FillTemplate(*reqEnv, text) : text;
    reqEnv->FormFieldRemove("text", 0);
    reqEnv->FormFieldInsert("text", text.data());
    reqEnv->SetSearchQuery(templated.data());
}

void TMisspelledSearchContext::DumpMisspellInfo() {
    TSearcherPropsRef props = CurrentContext->GetProperties();
    if (!props) {
        return;
    }

    {
        if (Result.Code == 201) {
            props->Set("MisspellFixed", Result.Suggest);
            props->Set("MisspellRule", Result.Rule);
            props->Set("MisspellSrcText", Result.SrcText);
            props->Set("MisspellRel", ToString(Result.Reliability));
        }
        props->Set("MisspellCode", ToString(Result.Code));
        props->Set("MisspellApplied", ToString(Applied));
        if (DumpResponse) {
            props->Set("MisspellFullResponse", Result.ServiceRespose);
        }
    }
}

void TMisspelledSearchContext::FetchMisspellInfo(TReqEnv* reqEnv) {
    if (ErratumProvider && !Result) {
        const TString& queryId = reqEnv->CgiParam.Get(NSearchProxyCgi::queryid);
        reqEnv->LogEvent<NEvClass::TDebugMessage>("misspell request started");
        Result = ErratumProvider->CorrectRequest(queryId, OriginalText);
        reqEnv->LogEvent<NEvClass::TDebugMessage>("misspell request finished: " + ToString(Result));
    }
}

void TMisspelledSearchContext::PrepareContext(ISearchContext& sc) {
    auto reqEnv = static_cast<TReqEnv*>(sc.ReqEnv());

    if (!Initialized) {
        Fill(reqEnv->CgiParam);
    }

    switch (Policy) {
    case TMisspellContext::EPolicy::Skip:
        SetText(OriginalText, reqEnv);
        break;
    case TMisspellContext::EPolicy::TryAtFirst:
        FetchMisspellInfo(reqEnv);
    case TMisspellContext::EPolicy::TrySkipAtFirst:
        if (Stage == TMisspellContext::EStage::Original) {
            SetText(OriginalText, reqEnv);
            break;
        } else {
            // fallthrough
        }
    case TMisspellContext::EPolicy::Force:
        FetchMisspellInfo(reqEnv);
        if (MinRel <= Result.Reliability && Result.Code == 201) {
            SetText(Result.Fixed, reqEnv);
            Stage = TMisspellContext::EStage::Misspelled;
            Applied = true;
        } else {
            SetText(OriginalText, reqEnv);
        }
        break;
    default:
        ythrow yexception() << "unexpected behaviour";
    }
}

bool TMisspelledSearchContext::ShouldContinue(int /*res*/, ISearchContext& sc) {
    if (!ErratumProvider) {
        return false;
    }

    if ((Policy == TMisspellContext::EPolicy::TryAtFirst || Policy == TMisspellContext::EPolicy::TrySkipAtFirst) && Stage == TMisspellContext::EStage::Original) {
        if (!sc.ReqEnv()->BaseSearchNotRespondCount() && !sc.Cluster()->TotalDocCount(0)) {
            Stage = TMisspellContext::EStage::Misspelled;
            return true; // no results found for original query, continue
        }
    }

    DumpMisspellInfo();
    return false;
}

TMisspelledSearchContext::TMisspelledSearchContext(const TMetaSearch& owner, ITemplateCorrectorPtr templateCorrector, IMisspellCorrectorPtr misspellCorrector)
    : TSequentialSearchContext(owner)
    , TemplateCorrector(templateCorrector)
    , ErratumProvider(misspellCorrector)
{
}

template <>
bool TryFromStringImpl<TMisspellContext::EPolicy>(const char* data, size_t /*length*/, TMisspellContext::EPolicy& policy) {
    if (strcmp(data, "try_at_first") == 0) {
        policy = TMisspellContext::EPolicy::TryAtFirst;
    } else if (strcmp(data, "try_skip_at_first") == 0) {
        policy = TMisspellContext::EPolicy::TrySkipAtFirst;
    } else if (strcmp(data, "no") == 0) {
        policy = TMisspellContext::EPolicy::Skip;
    } else {
        policy = TMisspellContext::EPolicy::Force;
    }

    return true;
}

template <>
void Out<TCorrectionResult>(IOutputStream& output, TTypeTraits<TCorrectionResult>::TFuncParam result) {
    output << result.Code << '\t' << result.Fixed << '\t' << result.Reliability << '\t' << result.Rule << '\t'
           << result.SrcText << '\t' << result.Suggest;
}
