package com.tutego.ch_05.batchOperation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.support.AbstractInterruptibleBatchPreparedStatementSetter;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;

@SpringBootApplication(scanBasePackageClasses = BatchedOperationModule.class)
public class BatchedOperationApplication {

    private static final List<Photo> PHOTOS = List.of(
            new Photo(1, "test_a", false, LocalDateTime.now().minusHours(2)),
            new Photo(2, "test_b", false, LocalDateTime.now())
    );

    // very bad abstraction from Spring
    private static final BatchPreparedStatementSetter batchPreparedStatementSetter = new BatchPreparedStatementSetter() {
        @Override
        public int getBatchSize() {
            return PHOTOS.size();
        }

        @Override
        public void setValues(PreparedStatement ps, int index) throws SQLException {
            var photo = PHOTOS.get(index);

            ps.setLong(1, photo.profile());
            ps.setString(2, photo.name());
            ps.setBoolean(3, photo.isProfilePhoto());
            ps.setTimestamp(4, Timestamp.valueOf(photo.created()));
        }
    };

    // useful when the stream of data is of undefined size, the batch size is then internally set to Integer.MAX_VALUE
    private static final AbstractInterruptibleBatchPreparedStatementSetter interruptibleBatchPreparedStatementSetter = new AbstractInterruptibleBatchPreparedStatementSetter() {
        @Override
        protected boolean setValuesIfAvailable(PreparedStatement ps, int i) throws SQLException {
            if (i >= PHOTOS.size())
                return false;

            batchPreparedStatementSetter.setValues(ps, i);
            return true;
        }
    };

    private static final ParameterizedPreparedStatementSetter<Photo> parameterizedPreparedStatementSetter = (ps, photo) -> {
        ps.setLong(1, photo.profile());
        ps.setString(2, photo.name());
        ps.setBoolean(3, photo.isProfilePhoto());
        ps.setTimestamp(4, Timestamp.valueOf(photo.created()));
    };

    public BatchedOperationApplication(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedJdbcTemplate) {
        var sql = "INSERT INTO photo (profile_fk, name, is_profile_photo, created) VALUES (?, ?, ?, ?)";

        jdbcTemplate.batchUpdate(
                sql,
                batchPreparedStatementSetter
        );

        jdbcTemplate.batchUpdate(
                sql,
                interruptibleBatchPreparedStatementSetter
        );

        jdbcTemplate.batchUpdate(
                sql,
                PHOTOS,
                PHOTOS.size(),
                parameterizedPreparedStatementSetter
        );

        var args = PHOTOS.stream()
                .map(photo -> new MapSqlParameterSource()
                        .addValue("profile", photo.profile())
                        .addValue("name", photo.name())
                        .addValue("is_profile_photo", photo.isProfilePhoto())
                        .addValue("created", photo.created()))
                .toArray(MapSqlParameterSource[]::new);


        // the NamedParameterJdbcTemplate algebra is wildly different from the JdbcTemplate api
        namedJdbcTemplate.batchUpdate(
                "INSERT INTO photo (profile_fk, name, is_profile_photo, created) VALUES (:profile, :name, :is_profile_photo, :created)",
                args
        );
    }

    public static void main(String... args) {
        SpringApplication.run(BatchedOperationApplication.class, args);
    }

}