package ru.yandex.qe.dispenser.domain.hierarchy.dao;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

import com.google.common.collect.Sets;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.qe.dispenser.domain.Project;
import ru.yandex.qe.dispenser.domain.Quota;
import ru.yandex.qe.dispenser.domain.QuotaSpec;
import ru.yandex.qe.dispenser.domain.Resource;
import ru.yandex.qe.dispenser.domain.dao.quota.QuotaDao;
import ru.yandex.qe.dispenser.domain.dao.quota.QuotaDaoImpl;

public final class CachingQuotaDao extends QuotaDaoImpl {
    private static final Logger LOG = LoggerFactory.getLogger(CachingQuotaDao.class);

    @NotNull
    private final QuotaDao directQuotaDao;
    private final long cacheRelevanceTime;

    private final long timestamp = System.currentTimeMillis();

    public CachingQuotaDao(@NotNull final QuotaDao directQuotaDao, final long cacheRelevanceTime) {
        this.directQuotaDao = directQuotaDao;
        this.cacheRelevanceTime = cacheRelevanceTime;
    }

    @Override
    public void createZeroQuotasFor(@NotNull final Collection<Project> project) {
    }

    @Override
    public void createZeroQuotasFor(@NotNull final QuotaSpec quotaSpec) {
    }

    @NotNull
    @Override
    public Set<Quota> getAll() {
        return readWithFallback(super::getAll, directQuotaDao::getAll);
    }

    @NotNull
    @Override
    public Set<Quota> getQuotasByResources(@NotNull final Collection<Resource> resources) {
        return readWithFallback(() -> super.getQuotasByResources(resources), () -> directQuotaDao.getQuotasByResources(resources));
    }

    @NotNull
    @Override
    public Set<Quota> getQuotasForUpdate(@NotNull final Collection<Quota.Key> keys) {
        final Set<Quota> cachedQuotas = super.getQuotasForUpdate(keys);
        final Set<Quota.Key> nocachedIdentifiers = new HashSet<>(keys);
        for (final Quota q : cachedQuotas) {
            nocachedIdentifiers.remove(q.getKey());
        }
        final Set<Quota> newQuotas = directQuotaDao.getQuotasForUpdate(nocachedIdentifiers);
        return Sets.union(newQuotas, cachedQuotas);
    }

    @NotNull
    private <T> T readWithFallback(@NotNull final Supplier<T> cacheReader, @NotNull final Supplier<T> databaseReader) {
        if (isRelevant()) {
            return cacheReader.get();
        }
        final long timeAfterUpdate = System.currentTimeMillis() - timestamp;
        LOG.warn("Hierarchy is not relevant! Fallback to database. Time after hierarchy update is {} ms", timeAfterUpdate);
        try {
            return databaseReader.get();
        } catch (RuntimeException e) {
            LOG.error("Database error! Fallback to foul hierarchy", e);
            return cacheReader.get();
        }
    }

    private boolean isRelevant() {
        return System.currentTimeMillis() - timestamp < cacheRelevanceTime;
    }
}
