package ru.yandex.qe.dispenser.domain.dao.project.role;

import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;

import com.google.common.base.Stopwatch;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.qe.dispenser.domain.ProjectRole;
import ru.yandex.qe.dispenser.domain.index.LongIndexBase;

@SuppressWarnings("UnstableApiUsage")
@ParametersAreNonnullByDefault
public class ProjectRoleCacheImpl implements ProjectRoleCache {

    private static final Logger LOG = LoggerFactory.getLogger(ProjectRoleCacheImpl.class);

    private static final Object CACHE_KEY = new Object();

    private final SqlProjectRoleDao projectRoleDao;

    private final LoadingCache<Object, ProjectRoleCache> projectRoleCache = CacheBuilder.newBuilder()
            .build(new CacheLoader<Object, ProjectRoleCache>() {
                @Override
                public ProjectRoleCache load(final Object key) {

                    final Stopwatch stopwatch = Stopwatch.createStarted();

                    final Set<ProjectRole> roles = projectRoleDao.getAll();

                    final Map<Long, ProjectRole> newRoles = roles.stream().collect(Collectors.toMap(LongIndexBase::getId, Function.identity()));

                    final Map<String, ProjectRole> roleByKey = roles.stream().collect(Collectors.toMap(ProjectRole::getKey, Function.identity()));

                    LOG.debug("ProjectRoleCache updated in {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));

                    return new ProjectRoleCache(newRoles, roleByKey);
                }
            });


    @Inject
    public ProjectRoleCacheImpl(final SqlProjectRoleDao projectRoleDao) {
        this.projectRoleDao = projectRoleDao;
    }

    @Override
    public Set<ProjectRole> getAll() {
        return Sets.newHashSet(getProjectRoleCache().rolesById.values());
    }

    private ProjectRoleCache getProjectRoleCache() {
        try {
            return projectRoleCache.get(CACHE_KEY);
        } catch (ExecutionException e) {
            throw new IllegalStateException("Can't fill project role cache");
        }
    }

    @Override
    @Nullable
    public ProjectRole getById(final long roleId) {
        return getProjectRoleCache().rolesById.get(roleId);
    }

    @Override
    @Nullable
    public ProjectRole getByKey(final String key) {
        return Objects.requireNonNull(getProjectRoleCache().rolesByKey.get(key), "No role with key '" + key + "'");
    }

    public void invalidate() {
        projectRoleCache.invalidateAll();
    }

    private static class ProjectRoleCache {
        private final Map<Long, ProjectRole> rolesById;
        private final Map<String, ProjectRole> rolesByKey;

        private ProjectRoleCache(final Map<Long, ProjectRole> rolesById, final Map<String, ProjectRole> rolesByKey) {
            this.rolesById = rolesById;
            this.rolesByKey = rolesByKey;
        }
    }
}
