package com.atlassian.stash.internal.server;

import com.atlassian.bitbucket.util.ValidationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Repository;

import javax.annotation.Nonnull;
import javax.validation.Validator;
import java.sql.ResultSet;
import java.sql.SQLException;

import static org.springframework.dao.support.DataAccessUtils.uniqueResult;

/**
 * Note that this is not a Hibernate DAO because it is used to initialize Hazelcast, and Hibernate depends on
 * Hazelcast for its caching.
 */
@DependsOn("bootstrapLiquibase")
@Repository("applicationPropertyDao")
public class JdbcApplicationPropertyDao implements ApplicationPropertyDao {

    private static final ApplicationPropertyMapper MAPPER = new ApplicationPropertyMapper();

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final Validator validator;

    @Autowired
    public JdbcApplicationPropertyDao(NamedParameterJdbcTemplate jdbcTemplate, Validator validator) {
        this.validator = validator;
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public ApplicationProperty create(@Nonnull ApplicationProperty property) {
        ValidationUtils.validate(validator, property);
        jdbcTemplate.update("INSERT INTO app_property (prop_key, prop_value) VALUES (:prop_key, :prop_value)",
                toParamSource(property));

        return property;
    }

    @Override
    public void deleteById(@Nonnull ApplicationProperty.Key key) {
        jdbcTemplate.update("DELETE FROM app_property WHERE prop_key = :prop_key", toParamSource(key));
    }

    @Override
    public ApplicationProperty getById(@Nonnull ApplicationProperty.Key key) {
        return uniqueResult(jdbcTemplate.query("SELECT prop_key, prop_value FROM app_property WHERE prop_key = :prop_key",
                toParamSource(key), MAPPER));
    }

    @Override
    public ApplicationProperty update(@Nonnull ApplicationProperty property) {
        ValidationUtils.validate(validator, property);
        int updated = jdbcTemplate.update("UPDATE app_property SET prop_value = :prop_value WHERE prop_key = :prop_key",
                toParamSource(property));

        if (updated != 1) {
            throw new IncorrectUpdateSemanticsDataAccessException("Incorrect number of rows updated: expected 1, actual " + updated);
        }

        return property;
    }

    @Override
    public boolean isSetup() {
        ApplicationProperty value = getById(ApplicationProperty.Key.SETUP_COMPLETED);
        return value != null && Boolean.parseBoolean(value.getValue());
    }

    private SqlParameterSource toParamSource(ApplicationProperty property) {
        return new MapSqlParameterSource()
                .addValue("prop_key", property.getKey().getId())
                .addValue("prop_value", property.getValue());
    }

    private SqlParameterSource toParamSource(ApplicationProperty.Key key) {
        return new MapSqlParameterSource("prop_key", key.getId());
    }

    private static class ApplicationPropertyMapper implements RowMapper<ApplicationProperty> {
        @Override
        public ApplicationProperty mapRow(ResultSet rs, int rowNum) throws SQLException {
            String id = rs.getString(1);
            String value = rs.getString(2);

            return new ApplicationProperty(ApplicationProperty.Key.fromId(id), value);
        }
    }

}

