package de.avatic.lcc.repositories.bulk;

import de.avatic.lcc.dto.bulk.BulkFileType;
import de.avatic.lcc.dto.bulk.BulkOperationState;
import de.avatic.lcc.dto.bulk.BulkProcessingType;
import de.avatic.lcc.model.bulk.BulkOperation;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Repository
public class BulkOperationRepository {


    private final JdbcTemplate jdbcTemplate;

    public BulkOperationRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional
    public Integer insert(BulkOperation operation) {

        removeOld(operation.getUserId());

        String sql = """
                INSERT INTO bulk_operation (user_id, bulk_file_type, bulk_processing_type, state, file, validity_period_id)
                VALUES (?, ?, ?, ?, ?, ?)
                """;

        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();

        var affectedRows = jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setInt(1, operation.getUserId());
            ps.setString(2, operation.getFileType().name());
            ps.setString(3, operation.getProcessingType().name());
            ps.setString(4, operation.getProcessState().name());
            ps.setBytes(5, operation.getFile());

            if (Objects.isNull(operation.getValidityPeriodId()))
                ps.setNull(6, java.sql.Types.INTEGER);
            else
                ps.setInt(6, operation.getValidityPeriodId());

            return ps;
        }, keyHolder);

        if (affectedRows != 1)
            throw new DatabaseException("Unable to schedule bulk operation");

        // Return the generated ID
        return keyHolder.getKey() != null ? keyHolder.getKey().intValue() : null;
    }

    @Transactional
    public void removeOld(Integer userId) {
        // First, update sys_error records to set bulk_operation_id to NULL
        // for bulk operations that will be deleted (all but the 10 newest for the current user)
        String updateErrorsSql = """
                UPDATE sys_error 
                SET bulk_operation_id = NULL 
                WHERE bulk_operation_id IN (
                    SELECT id FROM (
                        SELECT id 
                        FROM bulk_operation 
                        WHERE user_id = ? 
                        AND state NOT IN ('SCHEDULED', 'PROCESSING')
                        ORDER BY created_at DESC 
                        LIMIT 18446744073709551615 OFFSET 10
                    ) AS old_operations
                )
                """;

        jdbcTemplate.update(updateErrorsSql, userId);

        // Then delete the old bulk_operation entries (keeping only the 10 newest for the current user)
        String deleteBulkSql = """
                DELETE FROM bulk_operation 
                WHERE user_id = ? 
                AND state NOT IN ('SCHEDULED', 'PROCESSING')
                AND id NOT IN (
                    SELECT id FROM (
                        SELECT id 
                        FROM bulk_operation 
                        WHERE user_id = ? 
                        AND state NOT IN ('SCHEDULED', 'PROCESSING')
                        ORDER BY created_at DESC 
                        LIMIT 10
                    ) AS newest_operations
                )
                """;

        jdbcTemplate.update(deleteBulkSql, userId, userId);
    }

    @Transactional
    public void updateState(Integer id, BulkOperationState state) {
        String sql = """
                UPDATE bulk_operation
                SET state = ?
                WHERE id = ?
                """;

        jdbcTemplate.update(sql, state.name(), id);
    }

    @Transactional
    public List<BulkOperation> listByUserId(Integer userId) {

        cleanupTimeouts(userId);

        String sql = """
                SELECT id, user_id, bulk_file_type, bulk_processing_type, state, created_at, validity_period_id
                FROM bulk_operation
                WHERE user_id = ?
                
                ORDER BY created_at DESC LIMIT 10
                """;

        return jdbcTemplate.query(sql, new BulkOperationRowMapper(true), userId);
    }

    private void cleanupTimeouts(Integer userId) {

        String sql = """
                UPDATE bulk_operation SET state = 'EXCEPTION' WHERE user_id = ? AND (state = 'PROCESSING' OR state = 'SCHEDULED') AND created_at < NOW() - INTERVAL 60 MINUTE
                """;

        jdbcTemplate.update(sql, userId);
    }

    @Transactional
    public Optional<BulkOperation> getOperationById(Integer id) {
        String sql = """
                SELECT id, user_id, bulk_file_type, bulk_processing_type, state, file, created_at, validity_period_id
                FROM bulk_operation
                WHERE id = ?
                """;

        List<BulkOperation> results = jdbcTemplate.query(sql, new BulkOperationRowMapper(false), id);

        return Optional.ofNullable(results.isEmpty() ? null : results.getFirst());
    }

    @Transactional
    public void update(BulkOperation op) {
        String sql = """
                UPDATE bulk_operation
                SET user_id = ?, bulk_file_type = ?, state = ?, file = ?, validity_period_id = ?
                WHERE id = ?
                """;

        jdbcTemplate.update(sql,
                op.getUserId(),
                op.getFileType().name(),
                op.getProcessState().name(),
                op.getFile(),
                op.getValidityPeriodId(),
                op.getId()
        );
    }

    private record BulkOperationRowMapper(boolean skipFile) implements RowMapper<BulkOperation> {

        BulkOperationRowMapper() {
            this(false);
        }

        @Override
        public BulkOperation mapRow(ResultSet rs, int rowNum) throws SQLException {
            BulkOperation operation = new BulkOperation();
            operation.setId(rs.getInt("id"));
            operation.setUserId(rs.getInt("user_id"));
            operation.setProcessingType(BulkProcessingType.valueOf(rs.getString("bulk_processing_type")));
            operation.setFileType(BulkFileType.valueOf(rs.getString("bulk_file_type")));
            operation.setProcessState(BulkOperationState.valueOf(rs.getString("state")));


            operation.setValidityPeriodId(rs.getInt("validity_period_id"));
            if (rs.wasNull())
                operation.setValidityPeriodId(null);

            if (!skipFile)
                operation.setFile(rs.getBytes("file"));
            operation.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
            return operation;
        }
    }


}
