#pragma once

#include "config_wd.h"

#include <saas/rtyserver/docfetcher/library/consistent_options.h>
#include <saas/library/daemon_base/metrics/servicemetrics.h>
#include <saas/util/named_lock.h>

#include <yweb/realtime/distributor/client/distclient.h>

#include <util/generic/hash.h>

namespace NFusion {
    class ISignatureProvider {
    protected:
        ~ISignatureProvider(){} // non-virtual
    public:
        struct TSignature {
            TString Id;
            ui64 Version;
        };
    public:
        virtual TSignature Extract(const TString& data) const = 0;
    };

    TConsistentClientReplicas SplitReplicas(const NRealTime::TDistributors& distributors);

    class TConsistentClient: public NRealTime::TDistributorClientFileBase {
    public:
        using IClientInfoHandler = NRealTime::TDistributorClient::IClientInfoHandler;

        struct TReplicaStatus {
            NRealTime::TDistributorClient::TStatus ClientStatus;
            ui32 Age = 0;
            ui16 Id = 0;
            bool Primary = false;
        };
        using TStatus = TVector<TReplicaStatus>;
    public:
        struct TDocument {
        public:
            struct TSourceInfo {
                TVector<ui16> Replicas;
            };

            struct TVersion: public NRealTime::TDistributorClient::TSignature {
            public:
                TInstant Timestamp = TInstant::Zero();
                ui16 Replica = static_cast<ui16>(-1);
                ui16 VersionId = 0;
                ui16 FetchInProgress = 0;
                ui16 FetchFailed = 0;

            public:
                inline const TVector<TString>& GetAttributes() const {
                    return Attrs;
                }
                inline const TString& GetId() const {
                    return Id;
                }
                inline TInstant GetTimestamp() const {
                    return Timestamp;
                }
                inline ui64 GetVersion() const {
                    return Version;
                }
                inline ui64 GetKey() const {
                    return Key;
                }
                inline ui16 GetReplica() const {
                    return Replica;
                }
                inline bool HasData() const {
                    return !Data.empty();
                }
                inline const TString& GetData() const {
                    return Data;
                }
            };

            using TVersions = TVector<TVersion>;

        public:
            inline TDocument(const TVersion& version)
                : Id(version.GetId())
                , Data(version.GetData())
                , LastVersion(version.GetVersion())
                , FetchedVersion(version.HasData() ? version.GetVersion() : 0)
                , FetchFailedThreshold(0)
                , FetchLastFailed(false)
                , FetchAllFailed(false)
            {
                Versions.push_back(version);
            }

            TVersions Versions;
            TString Id;
            TString Data;
            ui64 LastVersion;
            ui64 FetchedVersion;
            ui16 FetchFailedThreshold;
            bool FetchLastFailed;
            bool FetchAllFailed;
        };

        using TDocumentPtr  = TAtomicSharedPtr<TDocument>;

        class TGuardedDocument {
        private:
            TDocumentPtr Document;
            TAtomicSharedPtr<NNamedLock::INamedLockManager::INamedLock> Lock;

        public:
            TGuardedDocument() {}
            TGuardedDocument(TDocumentPtr document, NNamedLock::TNamedLockPtr lock)
                : Document(document)
                , Lock(lock)
            {
            }

            inline TDocument* operator->() const noexcept {
                return Document.Get();
            }
            inline operator bool() const noexcept {
                return Document && Lock;
            }
            inline operator TDocumentPtr() const noexcept {
                return Document;
            }
        };

    private:
        using TDocuments    = TVector<TDocumentPtr>;
        using TDocumentsMap = THashMap<TString, size_t>;
        using TPriorities   = TVector<ui32>;

        struct TMetric: public TCompositeMetric {
            TPart<TOrangeMetric> Added;
            TPart<TOrangeMetric> Acquired;
            TPart<TOrangeMetric> Requested;
            TPart<TOrangeMetric> Unique;
            TPart<TOrangeMetric> FetchMiss;
            TPart<TOrangeMetric> FetchError;
            TPart<TOrangeMetric> LockMiss;
            TPart<TOrangeMetric> EmptySignature;
            TPart<TOrangeMetric> DataParsingError;
            TPart<TOrangeMetric> PrefetchMiss;
            TPart<TOrangeMetric> Rescheduled;

            TMetric(const TString& prefix)
                : Added(this, prefix + "_Added")
                , Acquired(this, prefix + "_Acquired")
                , Requested(this, prefix + "_Requested")
                , Unique(this, prefix + "_Unique")
                , FetchMiss(this, prefix + "_FetchMiss")
                , FetchError(this, prefix + "_FetchError")
                , LockMiss(this, prefix + "_LockMiss")
                , EmptySignature(this, prefix + "_EmptySignature")
                , DataParsingError(this, prefix + "_DataParsingError")
                , PrefetchMiss(this, prefix + "_PrefetchMiss")
                , Rescheduled(this, prefix + "_Rescheduled")
            {
            }
        };

        class TReplicaContext {
        public:
            class TIterator;

            struct TSignatureMetric: public TCompositeMetric {
                TPart<TOrangeMetric> Acquires;
                TPart<TOrangeMetric> Failures;

                TSignatureMetric(const TString& prefix)
                    : Acquires(this, prefix + "SignatureAcquires")
                    , Failures(this, prefix + "SignatureFailures")
                {
                }
            };
        public:
            TReplicaContext(const TConsistentClientReplica& replica, const TConsistentClientOptions& option);
            ~TReplicaContext();

            TReplicaContext::TIterator* GetIterator(size_t limit);
            void Fetch(TDocumentPtr doc, ui16 versionId);
            void SetPrimary();
            void SetSecondary();
            float GetFailRate() const;
            TReplicaStatus GetStatus() const;

            template <class F>
            void Run(F f);

            TString GetStatusMsg() const {
                return "Replica " + ToString(Replica.Id) + ": " + Client->GetStatusMsg();
            }
            ui32 GetAge() const {
                return Age;
            }
            ui16 GetId() const {
                return Replica.Id;
            }
            bool IsExhausted() const {
                return Client->IsExhausted();
            }
            bool IsPrimary() const {
                return Primary;
            }

        private:
            const TConsistentClientReplica Replica;
            const TConsistentClientOptions Options;
            THolder<NRealTime::TDistributorClient> Client;
            THolder<TIterator> Iterator;
            THolder<TSignatureMetric> SignatureMetric;
            TServiceMetricPtr FetchMetric;
            ui32 Age;
            bool Primary;

        private:
            bool GetNextVersion(TDocument::TVersion& version);
        };

        using TReplicaContexts = THashMap<ui16, TAtomicSharedPtr<TReplicaContext>>;

    private:
        const TConsistentClientOptions Options;
        TDocuments Records;
        TPriorities Priorities;
        TPriorities Shuffling;
        TDocumentsMap HashedIds;
        TReplicaContexts Contexts;
        THolder<TThread> Updater;

        std::atomic<size_t> Head;    // New object insertion position
        std::atomic<size_t> Reader;  // Current reader position

        TMetric Metric;
        TMutex DistributorLock;
        TMutex StorageLock;
        TMinRankCondition MinRank;
        std::atomic<bool> StopFlag;

    private:
        inline size_t GetIndex(size_t pos) const {
            return pos % Options.Capacity;
        }

        inline bool IsRead(size_t pos) const {
            return pos < Reader;
        }

        inline size_t Vacant() const {
            auto guard = Guard(StorageLock);
            Y_VERIFY(Head >= Reader);
            return Options.Capacity - (Head - Reader) - 1;
        }

        inline size_t Ahead() const {
            auto guard = Guard(StorageLock);
            return Head - Reader;
        }

        inline size_t AdvanceReader() {
            return Reader++;
        }

        inline size_t AdvanceHead() {
            return Head++;
        }

        bool IsSorted(TDocumentPtr doc) const;
        bool ShouldInsert(const TDocument::TVersion& version) const;

        ui32 AddVersion(const TDocument::TVersion& doc);
        bool GetVersion(TDocument::TVersion& doc, TDocument::TSourceInfo& info);

        ui32 AddDocument(TDocumentPtr doc);
        TDocumentPtr GetDocument(const TString& id) const;
        TDocumentPtr GetDocument(size_t pos) const;
        size_t GetPos(const TString& id) const;
        TGuardedDocument Guarded(TDocumentPtr doc);

        void Fetch(TDocumentPtr doc);
        bool CanFetch(TGuardedDocument doc) const;
        bool NeedFetch(TGuardedDocument doc) const;
        bool CheckFilterRank(TGuardedDocument doc) const;
        void ClearFetched(TGuardedDocument doc) const;
        void RescheduleOrDiscard(TGuardedDocument doc);
        void DiscardFiltered(TGuardedDocument doc) const;
        TDocument::TVersion GetFetched(TGuardedDocument doc) const;
        TDocument::TSourceInfo GetSourceInfo(TGuardedDocument doc) const;

        template <class F>
        void GuardedRun(F f);

        void Preload(size_t count); // signatures
        void Prefetch(size_t count); // data
        void ReassignReplicas();
        void Reset();
        void StartUpdater();
        void StopUpdater();
        static void* UpdaterProc(void* object);

    public:
        TConsistentClient(const TConsistentClientReplicas& replicas, const TConsistentClientOptions& option);
        ~TConsistentClient() override;

        using NRealTime::TDistributorClientFileBase::GetNextDoc;
        using NRealTime::TDistributorClientFileBase::GetNextRawRecord;
        bool GetNextDoc(::google::protobuf::Message& doc, NRealTime::TDistributorClient::TSignature& signature, TDocument::TSourceInfo& sourceInfo);
        bool GetNextRawRecord(NRealTime::TDistributorClient::TSignature& signature, TDocument::TSourceInfo& sourceInfo);

        void SetClientActive(bool value);
        void GetClientInfos(IClientInfoHandler* handler);

        bool IsExhausted() const;

        TStatus GetStatus() const;

    public: // IDistributorClient
        virtual bool GetNextRawRecordAndTime(TString& data, TInstant& docTime, TInstant& nowTime, ui64* key = nullptr, TVector<TString>* attrs = nullptr, ui64* distTime = nullptr) override;
        virtual bool GetNextDocAndTime(::google::protobuf::Message &doc, TInstant& docTime, TInstant& nowTime, ui64 *key = nullptr, TVector<TString>* attrs = nullptr, ui64* distTime = nullptr) override;
        virtual TString GetStatusMsg() const override;
        virtual void SetAge(int secondsAgo = 0) override;
        virtual void SetStream(const TString & stream) override;
        virtual void SetIdleTime(time_t idleTime) override;
        virtual void SetMaxChunks(size_t maxChunks) override;
        virtual void SetAttribute(const TString & attr) override;
        virtual void SetMinRank(double minRank) override;
        virtual void SetKeyRange(ui64 start, ui64 end) override;

        // unsupported
        virtual bool SetPreferredDistributor(const char* /*hostName*/, int /*port*/) override;
    };
}
