#include "context.h"

#include <saas/searchproxy/common/cgi.h>
#include <search/grouping/groupinfo.h>
#include <search/request/data/reqdata.h>

#include <library/cpp/http/misc/httpreqdata.h>
#include <library/cpp/scheme/scheme.h>

#include <util/generic/algorithm.h>
#include <library/cpp/string_utils/base64/base64.h>
#include <util/string/cast.h>
#include <library/cpp/cgiparam/cgiparam.h>
#include <util/string/type.h>
#include <util/string/vector.h>

void NSaas::NExperiments::TUserCtx::Fill(const TCgiParameters& /*cgi*/, const TSearchRequestData& rd) {
    auto expVersion = rd.HeaderIn("X-Yandex-ExpConfigVersion");
    auto expBoxes   = rd.HeaderIn("X-Yandex-ExpBoxes");
    auto expFlags   = rd.HeaderIn("X-Yandex-ExpFlags");
    if (expVersion && expBoxes && expFlags) {
        ConfigVersion = FromString<ui64>(*expVersion);
        Buckets = FromString<NSaas::NExperiments::TBuckets>(*expBoxes);

        NSc::TValue scheme;
        for (auto&& flag: SplitString(*expFlags, ",")) {
            const TString& decoded = Base64Decode(flag);
            const NSc::TValue& local = NSc::TValue::FromJsonThrow(decoded);
            if (local.IsArray()) {
                for (auto&& e : local.GetArray()) {
                    if (IsEligibleClient(e)) {
                        scheme.MergeUpdate(e["CONTEXT"]);
                    }
                }
            } else {
                if (IsEligibleClient(local)) {
                    scheme.MergeUpdate(local["CONTEXT"]);
                }
            }
        }

        for (auto&& bucket: Buckets) {
            FillParamsFromScheme(scheme, bucket.Id);
        }
    }
}

void NSaas::NExperiments::TUserCtx::FillParamsFromScheme(const NSc::TValue& scheme, ui64 id) {
    TExperimentParams& params = Experiments[id];

    const NSc::TValue& flags = scheme["SAAS"]["flags"][ToString(id)];
    if (IsIn(flags.GetArray(), "tdi")) {
        params.Type = TDI;
    } else if (IsIn(flags.GetArray(), "abtiot")) {
        params.Type = ABTIOT;
    } else {
        params.Type = ABT;
    }

    FillParamsFromScheme(scheme["A" + ToString(id)]["source"], params[A]);
    FillParamsFromScheme(scheme["B" + ToString(id)]["source"], params[B]);
    FillParamsFromScheme(scheme["GLOBAL"]["source"], params[A]);
    FillParamsFromScheme(scheme["GLOBAL"]["source"], params[B]);
}

void NSaas::NExperiments::TUserCtx::FillParamsFromScheme(const NSc::TValue& scheme, TSourceParams& params) {
    for (auto&& s : scheme.GetDict()) {
        const TStringBuf& source = s.first;
        for (auto&& p : s.second.GetDict()) {
            const TStringBuf& name = p.first;
            for (auto&& e : p.second.GetArray()) {
                const TSourceParam& param = {
                    TString(source),
                    TString(name),
                    e.ForceString()
                };
                params.push_back(param);
            }
        }
    }
}

bool NSaas::NExperiments::TUserCtx::IsEligibleClient(const NSc::TValue& /*scheme*/) {
    return true;
}

template <>
void Out<NSaas::NExperiments::TBucket>(IOutputStream& output, TTypeTraits<NSaas::NExperiments::TBucket>::TFuncParam type) {
    output << type.Id << "," << type.Slot << "," << type.Bucket;
}

template <>
void Out<NSaas::NExperiments::TBuckets>(IOutputStream& output, TTypeTraits<NSaas::NExperiments::TBuckets>::TFuncParam type) {
    output << JoinStrings(type.begin(), type.end(), ";");
}

template <>
NSaas::NExperiments::TBucket FromStringImpl<NSaas::NExperiments::TBucket>(const char* data, size_t length) {
    const TStringBuf s(data, length);
    auto splitted = SplitString(data, ",");
    if (splitted.size() != 3) {
        ythrow yexception() << "incorrect bucket: " << s;
    }

    NSaas::NExperiments::TBucket result = {
        FromString<i64>(splitted[0]),
        FromString<i64>(splitted[1]),
        FromString<i64>(splitted[2])
    };
    return result;
}

template <>
NSaas::NExperiments::TBuckets FromStringImpl<NSaas::NExperiments::TBuckets>(const char* data, size_t length) {
    const TStringBuf s(data, length);
    auto splitted = SplitString(data, ";");

    NSaas::NExperiments::TBuckets result;
    for (auto&& i : splitted) {
        result.push_back(FromString<NSaas::NExperiments::TBucket>(i));
    }
    return result;
}
