package com.atlassian.bitbucket.mesh.repository;

import com.atlassian.bitbucket.mesh.AbstractDao;
import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.sql.PreparedStatement;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Repository("repositoryAttributeDao")
public class JdbcRepositoryAttributeDao extends AbstractDao implements RepositoryAttributeDao {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public JdbcRepositoryAttributeDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    @Transactional
    public void delete(@Nonnull String id) {
        String sql = "DELETE FROM repository_attribute WHERE id = ?";

        jdbcTemplate.update(sql, id);
    }

    @Override
    public String getContentHash(@Nonnull String id) {
        String sql = "SELECT content_hash FROM repository_attribute WHERE id = ?";

        return jdbcTemplate.query(sql, rs -> rs.next() ? rs.getString(1) : null, id);
    }

    @Override
    public String getMetadataHash(@Nonnull String id) {
        String sql = "SELECT metadata_hash FROM repository_attribute WHERE id = ?";

        return jdbcTemplate.query(sql, rs -> rs.next() ? rs.getString(1) : null, id);
    }

    @Override
    public long getVersion(@Nonnull String id) {
        String sql = "SELECT version FROM repository_attribute WHERE id = ?";

        return jdbcTemplate.query(sql, rs -> rs.next() ? rs.getLong(1) : 1L, id);
    }

    @Nonnull
    @Override
    @Transactional
    public Map<String, Long> getVersionsByPartitions(@Nonnull Set<String> partitions) {
        String partitionClause = partitions.isEmpty() ? "id LIKE 'p/%'" : partitions.stream()
                .map(partition -> "id LIKE ?")
                .collect(Collectors.joining(" OR ", "(", ")"));

        String sql = "SELECT id, version FROM repository_attribute WHERE " + partitionClause +
                " ORDER BY id ASC";

        ImmutableMap.Builder<String, Long> builder = ImmutableMap.builder();
        Object[] parameters = partitions.stream()
                .map(partition -> "p/" + partition + "/%")
                .toArray();

        jdbcTemplate.query(sql, rs -> {
            builder.put(rs.getString(1), rs.getLong(2));
        }, parameters);
        return builder.build();
    }

    @Override
    @Transactional
    public long incrementAndGetVersion(@Nonnull String id) {
        String sql = "MERGE INTO repository_attribute USING DUAL " +
                "ON id = ? " +
                "WHEN MATCHED THEN " +
                "  UPDATE SET version = version + 1 " +
                "WHEN NOT MATCHED THEN " +
                "  INSERT (id, version) VALUES (?, ?)";
        jdbcTemplate.update(sql, id, id, 2L);

        return jdbcTemplate.queryForObject("SELECT version FROM repository_attribute WHERE id = ?", long.class, id);
    }

    @Override
    public boolean isReadOnly(@Nonnull String id) {
        String sql = "SELECT is_read_only FROM repository_attribute WHERE id = ?";

        Boolean readOnly = jdbcTemplate.query(sql, rs -> rs.next() && rs.getBoolean(1), id);
        return readOnly != null && readOnly;
    }

    @Override
    @Transactional
    public void replaceReadOnly(@Nonnull Collection<String> repositoryIds) {
        jdbcTemplate.update("UPDATE repository_attribute SET is_read_only = false");
        jdbcTemplate.batchUpdate("MERGE INTO repository_attribute (id, is_read_only) KEY (id) VALUES (?, true)",
                repositoryIds,
                500,
                (PreparedStatement ps, String repositoryId) -> ps.setString(1, repositoryId));
    }

    @Override
    public void setContentHash(@Nonnull String id, @Nullable String contentHash) {
        //This will update an existing row with the same ID, or insert a new row if the ID doesn't exist
        String sql = "MERGE INTO repository_attribute (id, content_hash) KEY (id) VALUES (?, ?)";
        jdbcTemplate.update(sql, id, contentHash);
    }

    @Override
    public void setMetadataHash(@Nonnull String id, @Nullable String metadataHash) {
        //This will update an existing row with the same ID, or insert a new row if the ID doesn't exist
        String sql = "MERGE INTO repository_attribute (id, metadata_hash) KEY (id) VALUES (?, ?)";
        jdbcTemplate.update(sql, id, metadataHash);
    }

    @Override
    @Transactional
    public void setReadOnly(@Nonnull String id, boolean readOnly) {
        //This will update an existing row with the same ID, or insert a new row if the ID doesn't exist
        String sql = "MERGE INTO repository_attribute (id, is_read_only) KEY (id) VALUES (?, ?)";
        jdbcTemplate.update(sql, id, readOnly);
    }

    @Override
    @Transactional
    public void setVersion(@Nonnull String id, long version) {
        //This will update an existing row with the same ID, or insert a new row if the ID doesn't exist
        String sql = "MERGE INTO repository_attribute (id, version) KEY (id) VALUES (?, ?)";
        jdbcTemplate.update(sql, id, version);
    }
}
