package ru.yandex.webmaster3.storage.host.moderation.camelcase.service;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.storage.mirrors.dao.DisplayNameCHDao;

@Service
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class DisplayNameService2 {
    private final DisplayNameCHDao mdbDisplayNameCHDao;
    private final ExecutorService executor = Executors.newFixedThreadPool(1);
    private static final Object KEY = new Object();

    private static final long CHECK_CACHE_DURATION_MINUTES = 5;

    private final LoadingCache<Object, Future<Map<WebmasterHostId, String>>> displayNameCache =
            CacheBuilder.newBuilder()
                    .refreshAfterWrite(CHECK_CACHE_DURATION_MINUTES, TimeUnit.MINUTES)
                    .build(new DisplayNameLoader());


    public void init() {
        displayNameCache.refresh(KEY);
    }

    public String getDisplayName(WebmasterHostId hostId) {
        return getDisplayName(hostId, false);
    }


    public String getDisplayName(WebmasterHostId hostId, boolean defaultUnicode) {
        return Optional.ofNullable(getDisplayNameWithCache(hostId))
                .orElseGet(() -> defaultUnicode ? hostId.getReadableHostname() : hostId.getPunycodeHostname());
    }

    // возвращает https://SSS.ru:1312
    public String getHostUrlWithDisplayName(WebmasterHostId hostId) {
        StringBuilder sb = new StringBuilder();
        if (hostId.getSchema() != WebmasterHostId.Schema.HTTP) {
            sb.append(hostId.getSchema().getSchemaPrefix());
        }

        String displayName = getDisplayName(hostId, true);
        sb.append(displayName);

        if (!hostId.isDefaultPort()) {
            sb.append(':').append(hostId.getPort());
        }
        return sb.toString();
    }

    private String getDisplayNameWithCache(WebmasterHostId hostId) {
        if (displayNameCache.getUnchecked(KEY).isDone()) {
            try {
                return displayNameCache.getUnchecked(KEY).get().get(hostId);
            } catch (Exception e) {
                log.error("Error preparing snapshot cache", e);
                displayNameCache.invalidate(KEY);
            }
        }
        return mdbDisplayNameCHDao.getDisplayName(hostId);
    }


    private final class DisplayNameLoader extends CacheLoader<Object, Future<Map<WebmasterHostId, String>>> {
        private String curVersion;

        @Override
        public Future<Map<WebmasterHostId, String>> load(Object ign) {
            String tableVersion = mdbDisplayNameCHDao.getTableVersion();
            return executor.submit(() -> loadDisplayNames(tableVersion));
        }

        @SneakyThrows
        public Map<WebmasterHostId, String> loadDisplayNames(String tableVersion) {
            log.info("start load display names cache - {}", DateTime.now());
            Map<WebmasterHostId, String> map;
            try {
                map = mdbDisplayNameCHDao.collectAll(tableVersion);
            } catch (Exception e) {
                log.error("while load display names cache error was occurred {}", e.toString(), e);
                throw e;
            }
            log.info("finish success load display names cache, size - {}, date - {}", map.size(), DateTime.now());
            curVersion = tableVersion;
            return map;
        }

        @Override
        public ListenableFuture<Future<Map<WebmasterHostId, String>>> reload(Object key, Future<Map<WebmasterHostId,
                String>> oldValue) {
            String tableVersion = mdbDisplayNameCHDao.getTableVersion();
            // обновляем кеш при смене базы
            log.debug("check reload display names cache");
            final ListenableFutureTask<Future<Map<WebmasterHostId, String>>> task;
            log.debug("oldValue.isDone() == {}, curBase - {}, base - {}", oldValue.isDone(), curVersion, tableVersion);
            if (!oldValue.isDone() || Objects.equals(curVersion, tableVersion)) {
                log.debug("old display names cache is actual");
                task = ListenableFutureTask.create(() -> oldValue);
            } else {
                log.debug("start reload display names cache ");
                task = ListenableFutureTask.create(() -> {
                            try {
                                final Map<WebmasterHostId, String> webmasterHostIdWebmasterHostIdMap =
                                        loadDisplayNames(tableVersion);
                                log.debug("finish reload display names cache ");
                                return CompletableFuture.completedFuture(webmasterHostIdWebmasterHostIdMap);
                            } catch (Exception e) {
                                return oldValue;
                            }
                        }
                );
            }
            executor.submit(task);
            return task;
        }
    }

}
