#include "client_hints.h"

#include <util/generic/singleton.h>

#include <balancer/kernel/module/conn_descr.h>

#include <metrika/core/libs/uahints/UAHintsHeaders.h>

#include <library/cpp/resource/resource.h>

namespace NSrvKernel {
    namespace {
        TUAAndUATraits TransformUAAndProto(
                NUATraits::IUserAgentTraits<>::TUserAgentAndProto& userAgentAndProto) {
            TUAAndUATraits restoredUAAndSerializedProtoUA;

            if (userAgentAndProto.RestoredUserAgent) {
                restoredUAAndSerializedProtoUA.RestoredUA =
                        std::move(*userAgentAndProto.RestoredUserAgent);
            }
            if (userAgentAndProto.ProtoUserAgent) {
                restoredUAAndSerializedProtoUA.SerializedUATraits =
                        userAgentAndProto.ProtoUserAgent.GetRef().SerializeAsString();
            }

            return restoredUAAndSerializedProtoUA;
        }

        class TUATraitsSettings {
        public:
            TUATraitsSettings(size_t restoredUserAgentAndProtoCacheSize, size_t protoFromFullUserAgentCacheSize)
                : RestoredUserAgentAndProtoCacheSize_(restoredUserAgentAndProtoCacheSize)
                , ProtoFromFullUserAgentCacheSize_(protoFromFullUserAgentCacheSize) {

                CHBro_ = NResource::Find("ch_browsers.xml");
                CHBuilder_ = NResource::Find("ch_builder.xml");
                CHRules_ = NResource::Find("ch_rules.xml");

                Bro_ = NResource::Find("browser.xml");
                Pro_ = NResource::Find("profiles.xml");
                Ext_ = NResource::Find("extra.xml");

                BroIStr_ = std::istringstream{std::string{Bro_.data(), Bro_.size()}};
                ProIStr_ = std::istringstream{std::string{Pro_.data(), Pro_.size()}};
                ExtIStr_ = std::istringstream{std::string{Ext_.data(), Ext_.size()}};
            }

            NUATraits::TUserAgentTraitsSettings Get(bool buildUAFromHints, bool buildProto) {
                NUATraits::TResourcesConfigs configs {
                    &CHBro_, &CHRules_, &CHBuilder_, BroIStr_, ProIStr_, ExtIStr_
                };


                return {
                    buildProto,
                    buildUAFromHints,
                    configs,
                    RestoredUserAgentAndProtoCacheSize_,
                    ProtoFromFullUserAgentCacheSize_
                };
            }
                
        private:
            size_t RestoredUserAgentAndProtoCacheSize_;
            size_t ProtoFromFullUserAgentCacheSize_;
            TString CHBro_;
            TString CHBuilder_;
            TString CHRules_;
            TString Bro_;
            TString Pro_;
            TString Ext_;
            std::istringstream BroIStr_;
            std::istringstream ProIStr_;
            std::istringstream ExtIStr_;
        };
            
    }

    void TUARestorerImpl::Init(bool buildUAFromHints, bool buildProto) noexcept {
        TUATraitsSettings settings(1000, 1000);
        UATraits_.Reset(NUATraits::CreateUserAgentTraits(settings.Get(buildUAFromHints, buildProto)));
    }

    TErrorOr<TUAAndUATraits> TUARestorerImpl::GetUAAndUATraits(
            const NUATraits::THintsHeaders& hintsHeaders,
            TStringBuf oldUserAgent) noexcept {
        try {
            NUATraits::IUserAgentTraits<>::TUserAgentAndProto uaAndUnserializedUATraits =
                    UATraits_->GetUserAgents(hintsHeaders, oldUserAgent, true);

            return TransformUAAndProto(uaAndUnserializedUATraits);
        } catch (...) {
            return Y_MAKE_ERROR(
                yexception{} << "UsegAgent restore library exception. Maybe headers size too large"
            );
        };
    }

    namespace {
        class TUATraitsProjection {
        public:
            TUATraits operator()(const TUserAgent& userAgent) noexcept {
                return {
                    .SameSiteNoneSupport = userAgent.GetSameSiteSupport()
                };
            }
        };
    }

    TCachedUATraits::TCachedUATraits() noexcept {
        TUATraitsSettings settings(1000, 10000);
        UATraits_.Reset(NUATraits::CreateUserAgentTraits<TUATraits, TUATraitsProjection>(settings.Get(false, true), TUATraitsProjection()));
    }

    TUATraits TCachedUATraits::GetUATraits(const TStringBuf uaHeader) noexcept {
        auto uaTraits = UATraits_->GetUserAgents(NUATraits::THintsHeaders(), uaHeader).ProtoUserAgent;
        if (uaTraits) {
            return *uaTraits;
        } else {
            return {};
        }
    }

    TUATraits TCachedUATraits::GetOrSetUATraits(const TConnDescr& descr) noexcept {
        if (!descr.Request) {
            return {};
        }
        auto& uaTraits = descr.Properties->Parent.UATraits;
        if (uaTraits) {
            return *uaTraits;
        }
        TStringBuf uaHeader = StripString(descr.Request->Headers().GetFirstValue("user-agent"));
        uaTraits = UATraits_->GetUserAgents(GetHintsHeaders(descr.Request->Headers()), uaHeader).ProtoUserAgent;
        if (uaTraits) {
            return *uaTraits;
        } else {
            return {};
        }
    }

    TCachedUATraits& GetCachedUATraits() noexcept {
        return *Singleton<TCachedUATraits>();
    }

    TUATraits GetUATraits(const TStringBuf uaHeader) noexcept {
        return GetCachedUATraits().GetUATraits(uaHeader);
    }

    TUATraits GetOrSetUATraits(const TConnDescr& descr) noexcept {
        return GetCachedUATraits().GetOrSetUATraits(descr);
    }
}
