package tech.easyflow.common.analyticaldb.core;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import tech.easyflow.common.analyticaldb.exception.AnalyticalDBException;
import tech.easyflow.common.analyticaldb.page.AnalyticalDBPageRequest;
import tech.easyflow.common.analyticaldb.page.AnalyticalDBPageResult;

import java.util.ArrayList;
import java.util.List;

public class DefaultAnalyticalDBOperations implements AnalyticalDBOperations {

    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public boolean available() {
        return jdbcTemplate != null;
    }

    @Override
    public void assertAvailable() {
        if (!available()) {
            throw new AnalyticalDBException("AnalyticalDB 数据源未启用");
        }
    }

    @Override
    public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) {
        assertAvailable();
        return jdbcTemplate.query(sql, rowMapper, args);
    }

    @Override
    public <T> T queryOne(String sql, Class<T> requiredType, Object... args) {
        assertAvailable();
        try {
            return jdbcTemplate.queryForObject(sql, requiredType, args);
        } catch (EmptyResultDataAccessException ex) {
            return null;
        }
    }

    @Override
    public <T> T queryOne(String sql, RowMapper<T> rowMapper, Object... args) {
        assertAvailable();
        List<T> rows = jdbcTemplate.query(sql, rowMapper, args);
        return rows.isEmpty() ? null : rows.get(0);
    }

    @Override
    public <T> List<T> queryForList(String sql, Class<T> elementType, Object... args) {
        assertAvailable();
        return jdbcTemplate.queryForList(sql, elementType, args);
    }

    @Override
    public int update(String sql, Object... args) {
        assertAvailable();
        return jdbcTemplate.update(sql, args);
    }

    @Override
    public <T> int[][] batchUpdate(String sql, List<T> items, int batchSize, ParameterizedPreparedStatementSetter<T> setter) {
        assertAvailable();
        if (items == null || items.isEmpty()) {
            return new int[0][0];
        }
        return jdbcTemplate.batchUpdate(sql, items, Math.max(batchSize, 1), setter);
    }

    @Override
    public <T> AnalyticalDBPageResult<T> page(String countSql,
                                              Object[] countArgs,
                                              String dataSql,
                                              Object[] dataArgs,
                                              AnalyticalDBPageRequest pageRequest,
                                              RowMapper<T> rowMapper) {
        assertAvailable();
        AnalyticalDBPageRequest request = pageRequest == null ? new AnalyticalDBPageRequest() : pageRequest;
        Long total = jdbcTemplate.queryForObject(countSql, Long.class, nullSafeArgs(countArgs));
        List<T> records = new ArrayList<>();
        if (total != null && total > 0) {
            Object[] argsWithPage = appendPageArgs(dataArgs, request.getSafePageSize(), request.getOffset());
            records = jdbcTemplate.query(dataSql + " LIMIT ? OFFSET ?", rowMapper, argsWithPage);
        }
        AnalyticalDBPageResult<T> result = new AnalyticalDBPageResult<>();
        result.setTotal(total == null ? 0L : total);
        result.setPageNumber(request.getPageNumber());
        result.setPageSize(request.getSafePageSize());
        result.setRecords(records);
        return result;
    }

    private Object[] appendPageArgs(Object[] args, int limit, int offset) {
        Object[] safeArgs = nullSafeArgs(args);
        Object[] merged = new Object[safeArgs.length + 2];
        System.arraycopy(safeArgs, 0, merged, 0, safeArgs.length);
        merged[safeArgs.length] = limit;
        merged[safeArgs.length + 1] = offset;
        return merged;
    }

    private Object[] nullSafeArgs(Object[] args) {
        return args == null ? new Object[0] : args;
    }
}
