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

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import ru.yandex.qe.dispenser.domain.CampaignOwningCost;

/**
 * Campaign owning cost cache.
 *
 * @author Ruslan Kadriev <aqru@yandex-team.ru>
 */
@Component
public class CampaignOwningCostCache {
    private final CampaignOwningCostDao campaignOwningCostDao;
    private final LoadingCache<Long, CampaignOwningCost> cache;

    public CampaignOwningCostCache(CampaignOwningCostDao campaignOwningCostDao) {
        this.campaignOwningCostDao = campaignOwningCostDao;
        this.cache = CacheBuilder.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .expireAfterAccess(24, TimeUnit.HOURS)
                .maximumSize(100)
                .build(new CampaignOwningCostCache.Loader(campaignOwningCostDao));
    }

    public Set<CampaignOwningCost> getAll() {
        return new HashSet<>(cache.asMap().values());
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void refresh() {
        Map<Long, CampaignOwningCost> refreshedById = campaignOwningCostDao.readAll().stream()
                .collect(Collectors.toMap(CampaignOwningCost::getCampaignId, Function.identity()));
        cache.putAll(refreshedById);
    }

    public void clear() {
        cache.invalidateAll();
    }

    private static final class Loader extends CacheLoader<Long, CampaignOwningCost> {
        private final CampaignOwningCostDao campaignOwningCostDao;

        private Loader(CampaignOwningCostDao campaignOwningCostDao) {
            this.campaignOwningCostDao = campaignOwningCostDao;
        }

        @Override
        public Map<Long, CampaignOwningCost> loadAll(@NotNull Iterable<? extends Long> keys) {
            Set<Long> keysSet = StreamSupport.stream(keys.spliterator(), false)
                    .collect(Collectors.toSet());
            return campaignOwningCostDao.readAll().stream()
                    .filter(campaignOwningCost -> keysSet.contains(campaignOwningCost.getCampaignId()))
                    .collect(Collectors.toMap(CampaignOwningCost::getCampaignId, Function.identity()));
        }

        @Override
        public CampaignOwningCost load(@NotNull Long key) {
            return campaignOwningCostDao.read(key);
        }
    }
}
