package com.atlassian.bitbucket.mesh.repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.Optional.empty;
import static java.util.Optional.of;

public class SimpleRepositoryAttributeDao implements RepositoryAttributeDao {

    private final Map<String, String> contentHashes;
    private final Map<String, String> metadataHashes;
    private final Set<String> readOnlyRepositories;
    private final Map<String, Long> versions;

    public SimpleRepositoryAttributeDao() {
        contentHashes = new ConcurrentHashMap<>();
        metadataHashes = new ConcurrentHashMap<>();
        versions = new ConcurrentHashMap<>();
        readOnlyRepositories = ConcurrentHashMap.newKeySet();
    }

    @Override
    public void delete(@Nonnull String id) {
        contentHashes.remove(id);
        metadataHashes.remove(id);
        versions.remove(id);
    }

    @Override
    public String getContentHash(@Nonnull String id) {
        return contentHashes.get(id);
    }

    @Override
    public String getMetadataHash(@Nonnull String id) {
        return metadataHashes.get(id);
    }

    @Override
    public long getVersion(@Nonnull String id) {
        return versions.getOrDefault(id, 1L);
    }

    @Nonnull
    @Override
    public Map<String, Long> getVersionsByPartitions(@Nonnull Set<String> partitions) {
        return versions.entrySet().stream()
                // sort by natural order for predictable order
                .sorted(Map.Entry.comparingByKey())
                //Only consider managed repository IDs that match the partition filter
                .filter(entry -> parsePartition(entry.getKey())
                        .map(partition -> partitions.isEmpty() || partitions.contains(partition))
                        .orElse(false))
                //toImmutableMap to preserve order
                .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    @Override
    public long incrementAndGetVersion(@Nonnull String id) {
        return versions.compute(id, (key, version) -> version == null ? 2L : version + 1);
    }

    @Override
    public boolean isReadOnly(@Nonnull String id) {
        return readOnlyRepositories.contains(id);
    }

    @Override
    public void replaceReadOnly(@Nonnull Collection<String> repositoryIds) {
        readOnlyRepositories.clear();
        readOnlyRepositories.addAll(repositoryIds);
    }

    @Override
    public void setContentHash(@Nonnull String id, @Nullable String contentHash) {
        if (contentHash != null) {
            contentHashes.put(id, contentHash);
        } else {
            contentHashes.remove(id);
        }
    }

    @Override
    public void setMetadataHash(@Nonnull String id, @Nullable String metadataHash) {
        if (metadataHash != null) {
            metadataHashes.put(id, metadataHash);
        } else {
            metadataHashes.remove(id);
        }
    }

    @Override
    public void setReadOnly(@Nonnull String id, boolean readOnly) {
        if (readOnly) {
            readOnlyRepositories.add(id);
        } else {
            readOnlyRepositories.remove(id);
        }
    }

    @Override
    public void setVersion(@Nonnull String id, long version) {
        versions.put(id, version);
    }

    private static Optional<String> parsePartition(String id) {
        if (id == null || id.length() < 4 || !id.startsWith("p/")) {
            return empty();
        }
        int index = id.indexOf('/', 2);
        return index > 0 ? of(id.substring(2, index)) : empty();
    }
}
