#pragma once

#include "url_parts.h"
#include <balancer/modules/redirects/redirects.cfgproto.pb.h>

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

#include <library/cpp/containers/comptrie/comptrie_builder.h>

#include <util/generic/maybe.h>
#include <util/generic/ptr.h>
#include <util/generic/strbuf.h>
#include <util/generic/string.h>
#include <util/generic/variant.h>
#include <util/generic/vector.h>

#include <contrib/libs/re2/re2/re2.h>

#include <tuple>

namespace NModRedirects {
    using namespace NSrvKernel;

    struct TRedirect {
        TString Location;
        ui16 Code = 302;

    public:
        auto AsTuple() const noexcept {
            return std::make_tuple(TStringBuf(Location), Code);
        }

        bool operator==(const TRedirect& other) const noexcept {
            return AsTuple() == other.AsTuple();
        }
    };


    struct TForward {
        TString Location;
        const IModule* Dst = nullptr;

    public:
        auto AsTuple() const noexcept {
            return std::make_tuple(TStringBuf(Location), Dst);
        }

        bool operator==(const TForward& other) const noexcept {
            return AsTuple() == other.AsTuple();
        }
    };


    using TResult = std::variant<std::monostate, TRedirect, TForward>;

    namespace NImpl {
        enum class EPlace {
            Path        /*"{path}"*/,
            S_Path      /*"{/path}"*/,
            Query       /*"{query}"*/,
            A_Query     /*"{&query}"*/,
            Q_Query     /*"{?query}"*/,
        };

        using TTemplate = TVector<std::variant<TString, EPlace>>;

        struct TRewrite : TNonCopyable {
            TUrlParts Parts;
            re2::RE2 Regexp;
            TString Pattern;
            bool Global = false;

        public:
            TRewrite(const TRewriteConfig& cfg);

            TString Apply(TStringBuf loc) const noexcept;
        };

        struct TRedirectAction {
            ui16 Code = -1;
        };

        struct TForwardAction {
            const IModule* Dst = nullptr;
        };

        using TAction = std::variant<std::monostate, TRedirectAction, TForwardAction>;

        struct TDst : TMoveOnly {
            TTemplate Location;
            TDeque<TRewrite> DstRewrites;
            ui32 ToReserve = 0;
            TAction Action;
            bool LegacyRStrip = false;
        };

        TTemplate GenLocation(const TStringBuf rawDst);
        TString GenSrc(TStringBuf src, bool expectsWcard);

        TResult GenResult(const TDst& dst, TStringBuf path, TStringBuf query) noexcept;
    }

    class TRedirects {
        using TTrie = TCompactTrie<char, ui64>;
        using TBuilder = TTrie::TBuilder;
    public:
        void AddRedirect(const TString& src, const TRedirectConfig& cfg);
        void AddForward(const TString& src, const TForwardConfig& cfg, const IModule& fwd);

        void Compile();

        TResult Location(TStringBuf host, TStringBuf path, TStringBuf query) const noexcept;

    private:
        void AddRoute(
            const TString& srcLoc,
            const TString& dstLoc,
            const TVector<TRewriteConfig>& rwr,
            const NImpl::TAction& act,
            bool legacyRStrip);
        const NImpl::TDst* FindDst(TTrie suffixes, TStringBuf suffix) const noexcept;

    private:
        TBuilder Builder_;
        TVector<NImpl::TDst> Dsts_;
        TTrie Srcs_;
    };
}
