#include "snapshot_manager.h"

#include <library/cpp/logger/global/global.h>

const IStopCondition& IStopCondition::NonStop() {
    struct TNonStop : IStopCondition {
        bool Stopped() const final {
            return false;
        }
    };
    static TNonStop nonStop;
    return nonStop;
}

ISnapshotFilter::TPtr ISnapshotFilter::AcceptAll() {
    struct TAcceptAll : ISnapshotFilter {
        bool IsAccepted(TInstant /*snapshotTime*/) const final {
            return true;
        }
    };

    return MakeHolder<TAcceptAll>();
}

ISnapshotFilter::TPtr ISnapshotFilter::TimestampRange(TInstant newerThan, TInstant olderOrEqualTo) {
    struct TTimestampRange : ISnapshotFilter {
        TTimestampRange(TInstant newerThan, TInstant olderOrEqualTo)
            : NewerThan(newerThan)
            , OlderThanOrEqualTo(olderOrEqualTo)
        {
        }

        bool IsAccepted(TInstant snapshotTime) const final {
            if (snapshotTime <= NewerThan) {
                INFO_LOG << "Skipping " << snapshotTime.Seconds()
                    << ": it's older than (or equal to) " << NewerThan.Seconds() << Endl;
                
                return false;
            }
            if (snapshotTime > OlderThanOrEqualTo) {
                INFO_LOG << "Skipping " << snapshotTime.Seconds()
                    << ": it's newer than " << OlderThanOrEqualTo.Seconds() << Endl;
                
                return false;
            }
            return true;
        }

    private:
        TInstant NewerThan;
        TInstant OlderThanOrEqualTo;
    };

    return MakeHolder<TTimestampRange>(newerThan, olderOrEqualTo);
}

ISnapshotFilter::TPtr ISnapshotFilter::FixedTimestamp(TInstant timestamp) {
    return TimestampRange(timestamp - TDuration::Seconds(1), timestamp);
}

IIndexSnapshotManager::IIndexSnapshotManager(TContext context)
    : Context(std::move(context))
{
}

void IIndexSnapshotManager::PublishSnapshot(const TSnapshot& snapshot) {
    DoWithRetry(std::bind(&TSelf::DoPublishSnapshot, this, std::cref(snapshot)), Context.RetryOptions, true);
}

std::tuple<IIndexSnapshotManager::TSnapshots, bool> IIndexSnapshotManager::GetSnapshots(TInstant newerThan, TInstant olderThanOrEqualTo, const IStopCondition& stopper) {
    const THolder<ISnapshotFilter> snapshotFilter = ISnapshotFilter::TimestampRange(newerThan, olderThanOrEqualTo);
    bool hasPriorSnapshots = false;
    const auto filter = [&](TInstant timestamp) {
        if (timestamp <= newerThan) {
            hasPriorSnapshots = true;
        }
        return snapshotFilter->IsAccepted(timestamp);
    };

    std::function<TSnapshots()> snapshotsGetter = [this, &filter, &stopper]() -> TSnapshots { return DoGetSnapshots(filter, stopper); };
    TSnapshots snapshots = DoWithRetry(snapshotsGetter, Context.RetryOptions, true).GetRef();

    std::sort(snapshots.begin(), snapshots.end(), [](const TSnapshot& a, const TSnapshot& b) -> bool {
        return a.Timestamp > b.Timestamp;
    });
    return std::make_tuple(snapshots, hasPriorSnapshots);
}

IIndexSnapshotManager::TSnapshots IIndexSnapshotManager::GetSnapshotsFixed(TInstant timestamp, const IStopCondition& stopper) {
    const THolder<ISnapshotFilter> snapshotFilter = ISnapshotFilter::FixedTimestamp(timestamp);
    const auto filter = [&](TInstant timestamp) {
        return snapshotFilter->IsAccepted(timestamp);
    };
    std::function<TSnapshots()> f = [this, &filter, &stopper]() -> TSnapshots { return DoGetSnapshots(filter, stopper); };
    return DoWithRetry(f, Context.RetryOptions, true).GetRef();
}

IIndexSnapshotManager::TShardResources IIndexSnapshotManager::GetResourcesForShard(NUtil::TInterval<ui64> shard, TInstant newerThan, TInstant olderThanOrEqualTo, const IStopCondition& stopper) {
    auto [snapshots, _] = GetSnapshots(newerThan, olderThanOrEqualTo, stopper);
    TShardResources resources;
    for (auto &snapshot : snapshots) {
        if (auto resource = FindResourceInSnapshot(snapshot, shard)) {
            resources.push_back(*resource);
        }
    }
    return resources;
}

IIndexSnapshotManager::TShardResources IIndexSnapshotManager::GetResourcesForShard(NUtil::TInterval<ui64> shard, TDuration maxAge, const IStopCondition& stopper) {
    return GetResourcesForShard(shard, TInstant::Now() - maxAge, TInstant::Max(), stopper);
}

std::tuple<IIndexSnapshotManager::TGroupedShardResources, bool> IIndexSnapshotManager::GetGroupedByTSResourcesForShard(NUtil::TInterval<ui64> shard, TInstant newerThan, TInstant olderThanOrEqualTo, const IStopCondition& stopper) {
    auto [snapshots, hasPriorSnapshots] = GetSnapshots(newerThan, olderThanOrEqualTo, stopper);
    TGroupedShardResources resources;
    for (auto &snapshot : snapshots) {
        if (auto resource = FindResourceInSnapshot(snapshot, shard)) {
            resources[snapshot.Timestamp].push_back(*resource);
        }
    }
    TVector<TInstant> TimestampsToSkip;
    for (const auto &resourceGroup : resources) {
        if (!Context.UseGroups && resources[resourceGroup.first].size() > 1) {
            INFO_LOG << "Skipping " << resourceGroup.first.Seconds()
                << ": It is a group of resources and using groups is disabled now (Context.UseGroups)" << Endl;
            TimestampsToSkip.push_back(resourceGroup.first);
        }
    }
    for (const auto &timestamp : TimestampsToSkip) {
        resources.erase(timestamp);
    }
    for (const auto &resourceGroup : resources) {
        std::sort(resources[resourceGroup.first].begin(),
                    resources[resourceGroup.first].end(),
                    [](const NRTYServer::TShardResource &a, const NRTYServer::TShardResource &b) -> bool {
                        return a.GetTimestampEx() < b.GetTimestampEx();
                });
    }
    return std::make_tuple(resources, hasPriorSnapshots);
}

std::tuple<IIndexSnapshotManager::TGroupedShardResources, bool> IIndexSnapshotManager::GetGroupedByTSResourcesForShard(NUtil::TInterval<ui64> shard, TDuration maxAge, const IStopCondition& stopper) {
    return GetGroupedByTSResourcesForShard(shard, TInstant::Now() - maxAge, TInstant::Max(), stopper);
}

IIndexSnapshotManager::TShardResources IIndexSnapshotManager::GetResourceForShardFixed(NUtil::TInterval<ui64> shard, TInstant timestamp, const IStopCondition& stopper) {
    auto snapshots = GetSnapshotsFixed(timestamp, stopper);
    for (auto &snapshot : snapshots) {
        if (auto resource = FindResourceInSnapshot(snapshot, shard)) {
            return { *resource };
        }
    }
    return { };
}

const IIndexSnapshotManager::TShardResource* IIndexSnapshotManager::FindResourceInSnapshot(const TSnapshot& snapshot, NUtil::TInterval<ui64> shard) {
    const IIndexSnapshotManager::TShardResource* result = nullptr;
    for (auto &shardResource : snapshot.Shards.GetShard()) {
        if (NUtil::TInterval<ui64>(shardResource.GetShardMin(), shardResource.GetShardMax()) == shard) {
            if (!result) {
                result = &shardResource;
            } else {
                WARNING_LOG << "Duplicate of shard " << shard << " in snapshot" << snapshot.Timestamp.Seconds() << Endl;
            }
        }
    }
    if (!result) {
        WARNING_LOG << "Snapshot " << snapshot.Timestamp.Seconds() << " doesn't contain shard " << shard << Endl;
    }
    return result;
}
