package ru.yandex.webmaster3.storage.searchurl.offline;

import java.util.NavigableMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.Instant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.searchbase.SearchBaseDates;
import ru.yandex.webmaster3.core.searchbase.SearchBaseUpdateInfo;
import ru.yandex.webmaster3.core.util.W3Collectors;
import ru.yandex.webmaster3.storage.searchbase.SearchBaseUpdatesService;
import ru.yandex.webmaster3.storage.searchurl.offline.dao.SearchBaseImportTablesRepository;
import ru.yandex.webmaster3.storage.searchurl.offline.dao.SearchBaseImportedTable;
import ru.yandex.webmaster3.storage.searchurl.offline.data.SearchBaseImportInfo;
import ru.yandex.webmaster3.storage.searchurl.offline.data.SearchBaseImportStageEnum;
import ru.yandex.webmaster3.storage.searchurl.offline.data.SearchBaseImportTaskType;

/**
 * @author avhaliullin
 */
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SearchBaseImportTablesService {
    private static final long CACHE_EXPIRATION_MINUTES = 5;

    private final SearchBaseUpdatesService searchBaseUpdatesService;
    private final SearchBaseImportTablesRepository searchBaseImportTablesCDao;

    private LoadingCache<SearchBaseImportTaskType, NavigableMap<Instant, SearchBaseImportInfo>> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(CACHE_EXPIRATION_MINUTES, TimeUnit.MINUTES)
            .build(CacheLoader.from(this::getBasesDirectly));

    private NavigableMap<Instant, SearchBaseImportInfo> getBasesDirectly(SearchBaseImportTaskType taskType) {
        return searchBaseImportTablesCDao.listBases(taskType)
                .stream()
                .collect(W3Collectors.toTreeMap(SearchBaseImportInfo::getSearchBaseDate, Function.identity()));
    }

    private NavigableMap<Instant, SearchBaseImportInfo> getBasesCached(SearchBaseImportTaskType taskType) {
        return cache.getUnchecked(taskType);
    }

    public SearchBaseImportedTable getTable(SearchBaseImportTaskType taskType) {
        NavigableMap<Instant, SearchBaseImportInfo> tables = getBasesCached(taskType);
        SearchBaseDates searchBaseDates = searchBaseUpdatesService.getSearchBaseUpdates();
        for (Instant baseCollectDate : searchBaseDates.getCollectionDate2Info().descendingKeySet()) {
            SearchBaseImportInfo result = tables.get(baseCollectDate);
            if (result != null && result.getStage() == SearchBaseImportStageEnum.READY) {
                SearchBaseUpdateInfo updateInfo = searchBaseDates.getCollectionDate2Info().get(baseCollectDate);
                return new SearchBaseImportedTable(result.getTaskType(), result.getDbName(), result.getReadTableName(),
                        updateInfo, result.getShardsCount(), result.getPartsCount());
            }
        }
        throw new WebmasterException("No loaded tables found for type " + taskType + " with search base switched",
                new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null));
    }

    public Pair<SearchBaseImportInfo, SearchBaseUpdateInfo> getLatestSwitcherInfo(SearchBaseDates baseDates, SearchBaseImportTaskType taskType) {
        NavigableMap<Instant, SearchBaseImportInfo> tables = getBasesCached(taskType);
        for (Instant baseCollectDate : baseDates.getCollectionDate2Info().descendingKeySet()) {
            SearchBaseImportInfo result = tables.get(baseCollectDate);
            if (result != null && result.getStage() == SearchBaseImportStageEnum.READY) {
                SearchBaseUpdateInfo baseUpdateInfo =
                        baseDates.getCollectionDate2Info().get(baseCollectDate);
                return Pair.of(result, baseUpdateInfo);
            }
        }
        throw new WebmasterException("No loaded tables found for type " + taskType + " with search base switched",
                new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null));
    }

    public Pair<SearchBaseImportInfo, SearchBaseUpdateInfo> getLatestSwitcherInfo(SearchBaseImportTaskType taskType) {
        return getLatestSwitcherInfo(searchBaseUpdatesService.getSearchBaseUpdates(), taskType);
    }

}
