package tech.easyflow.chatlog.repository.analyticaldb;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import tech.easyflow.chatlog.domain.dto.ChatDashboardSummary;
import tech.easyflow.chatlog.domain.query.ChatSessionFilterQuery;
import tech.easyflow.chatlog.support.ChatJsonSupport;
import tech.easyflow.common.analyticaldb.config.AnalyticalDBFlywayProperties;
import tech.easyflow.common.analyticaldb.core.AnalyticalDBOperations;
import tech.easyflow.common.analyticaldb.page.AnalyticalDBPageRequest;
import tech.easyflow.common.analyticaldb.page.AnalyticalDBPageResult;
import tech.easyflow.common.analyticaldb.support.AnalyticalDBHealthSupport;

import java.math.BigInteger;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * {@link ChatAnalyticalDBRepository} 测试。
 */
public class ChatAnalyticalDBRepositoryTest {

    /**
     * 验证会话分页的统计 SQL 与数据 SQL 使用相同的 Agent 类型和 ID 条件。
     */
    @Test
    public void shouldApplyAssistantCodeAndIdToSessionPageQueries() {
        RecordingAnalyticalDBOperations operations = new RecordingAnalyticalDBOperations();
        ChatSessionFilterQuery query = new ChatSessionFilterQuery();
        query.setAssistantCode("AGENT");
        query.setAssistantId(BigInteger.TEN);

        ChatAnalyticalDBRepository repository = newRepository(operations);
        repository.pageSessions(query);

        Assert.assertTrue(operations.lastPageCountSql.contains("assistant_code=?"));
        Assert.assertTrue(operations.lastPageCountSql.contains("assistant_id=?"));
        Assert.assertTrue(operations.lastPageDataSql.contains("assistant_code=?"));
        Assert.assertTrue(operations.lastPageDataSql.contains("assistant_id=?"));
        Assert.assertArrayEquals(new Object[]{"AGENT", BigInteger.TEN}, operations.lastPageCountArgs);
        Assert.assertArrayEquals(new Object[]{"AGENT", BigInteger.TEN}, operations.lastPageDataArgs);
    }

    /**
     * 验证工作台汇总使用跨天去重的 session 口径。
     */
    @Test
    public void shouldUseDistinctSessionSqlForDashboardSummary() {
        RecordingAnalyticalDBOperations operations = new RecordingAnalyticalDBOperations();
        operations.queryOneResult = new ChatDashboardSummary(2L, 5L, 1L, 1L);

        ChatAnalyticalDBRepository repository = newRepository(operations);
        repository.queryDashboardSummary(LocalDate.of(2026, 4, 1), LocalDate.of(2026, 4, 8), BigInteger.ONE);

        Assert.assertNotNull(operations.lastQueryOneSql);
        Assert.assertTrue(operations.lastQueryOneSql.contains("FROM dws_chat_session_day agg"));
        Assert.assertTrue(operations.lastQueryOneSql.contains("uniqExact(agg.dimension_id) AS session_total"));
        Assert.assertTrue(operations.lastQueryOneSql.contains("uniqExact(agg.user_id) AS active_user_total"));
    }

    /**
     * 验证智能体使用榜按去重会话数排序，并同时统计用户数。
     */
    @Test
    public void shouldUseDistinctSessionSqlForAssistantUsageRanks() {
        RecordingAnalyticalDBOperations operations = new RecordingAnalyticalDBOperations();

        ChatAnalyticalDBRepository repository = newRepository(operations);
        repository.queryAssistantUsageRanks(LocalDate.of(2026, 4, 1), LocalDate.of(2026, 4, 8), BigInteger.ONE, 5);

        Assert.assertNotNull(operations.lastQuerySql);
        Assert.assertTrue(operations.lastQuerySql.contains("FROM dws_chat_session_day agg"));
        Assert.assertTrue(operations.lastQuerySql.contains("uniqExact(agg.user_id) AS user_total"));
        Assert.assertTrue(operations.lastQuerySql.contains("uniqExact(agg.dimension_id) AS session_total"));
        Assert.assertTrue(operations.lastQuerySql.contains(
                "ORDER BY agg.session_total DESC, agg.user_total DESC, agg.message_total DESC, agg.assistant_id ASC"
        ));
        Assert.assertTrue(operations.lastQuerySql.contains("agg.assistant_id AS assistant_id"));
        Assert.assertTrue(operations.lastQuerySql.contains("snapshot.assistant_name AS assistant_name"));
    }

    /**
     * 验证智能体趋势查询显式返回 assistant_id 别名，避免 ClickHouse JDBC 无法按列名映射。
     */
    @Test
    public void shouldAliasAssistantIdForAssistantTrendQueries() {
        RecordingAnalyticalDBOperations operations = new RecordingAnalyticalDBOperations();

        ChatAnalyticalDBRepository repository = newRepository(operations);
        repository.queryAssistantSessionTrends(
                LocalDate.of(2026, 4, 1),
                LocalDate.of(2026, 4, 8),
                BigInteger.ONE,
                List.of(BigInteger.ONE, BigInteger.TWO)
        );

        Assert.assertNotNull(operations.lastQuerySql);
        Assert.assertTrue(operations.lastQuerySql.contains("agg.assistant_id AS assistant_id"));
        Assert.assertTrue(operations.lastQuerySql.contains("snapshot.assistant_name AS assistant_name"));
        Assert.assertTrue(operations.lastQuerySql.contains("agg.assistant_id IN (?, ?)"));
    }

    /**
     * 验证智能体趋势查询在 Top 列表包含空 assistant_id 时会补上 IS NULL 条件。
     */
    @Test
    public void shouldSupportNullAssistantIdInAssistantTrendQueries() {
        RecordingAnalyticalDBOperations operations = new RecordingAnalyticalDBOperations();

        ChatAnalyticalDBRepository repository = newRepository(operations);
        repository.queryAssistantSessionTrends(
                LocalDate.of(2026, 4, 1),
                LocalDate.of(2026, 4, 8),
                BigInteger.ONE,
                Arrays.asList(BigInteger.ONE, null)
        );

        Assert.assertNotNull(operations.lastQuerySql);
        Assert.assertTrue(operations.lastQuerySql.contains("(agg.assistant_id IN (?) OR agg.assistant_id IS NULL)"));
    }

    /**
     * 验证用户活跃榜支持按智能体过滤，且导出场景可不带 limit。
     */
    @Test
    public void shouldSupportAssistantFilterAndUnlimitedUserRanks() {
        RecordingAnalyticalDBOperations operations = new RecordingAnalyticalDBOperations();

        ChatAnalyticalDBRepository repository = newRepository(operations);
        repository.queryActiveUserRanks(
                LocalDate.of(2026, 4, 1),
                LocalDate.of(2026, 4, 8),
                BigInteger.ONE,
                BigInteger.TEN,
                null
        );

        Assert.assertNotNull(operations.lastQuerySql);
        Assert.assertTrue(operations.lastQuerySql.contains("AND agg.assistant_id = ?"));
        Assert.assertTrue(operations.lastQuerySql.contains("ORDER BY agg.session_total DESC, agg.message_total DESC, agg.user_id ASC"));
        Assert.assertFalse(operations.lastQuerySql.contains("LIMIT ?"));
    }

    /**
     * 构造仓储实例。
     *
     * @param operations 分析库操作桩
     * @return 仓储实例
     */
    private ChatAnalyticalDBRepository newRepository(RecordingAnalyticalDBOperations operations) {
        StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
        beanFactory.addBean("analyticalDBOperations", operations);
        ObjectProvider<AnalyticalDBOperations> provider = beanFactory.getBeanProvider(AnalyticalDBOperations.class);
        AnalyticalDBHealthSupport healthSupport =
                new AnalyticalDBHealthSupport(provider, new AnalyticalDBFlywayProperties());
        ChatJsonSupport jsonSupport = new ChatJsonSupport(new ObjectMapper());
        return new ChatAnalyticalDBRepository(provider, healthSupport, jsonSupport);
    }

    /**
     * 记录 SQL 的分析库桩实现。
     */
    private static class RecordingAnalyticalDBOperations implements AnalyticalDBOperations {

        private String lastQueryOneSql;
        private String lastQuerySql;
        private String lastPageCountSql;
        private String lastPageDataSql;
        private Object[] lastPageCountArgs;
        private Object[] lastPageDataArgs;
        private ChatDashboardSummary queryOneResult;

        @Override
        public boolean available() {
            return true;
        }

        @Override
        public void assertAvailable() {
        }

        @Override
        public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) {
            this.lastQuerySql = sql;
            return Collections.emptyList();
        }

        @Override
        public <T> T queryOne(String sql, Class<T> requiredType, Object... args) {
            this.lastQueryOneSql = sql;
            return null;
        }

        @SuppressWarnings("unchecked")
        @Override
        public <T> T queryOne(String sql, RowMapper<T> rowMapper, Object... args) {
            this.lastQueryOneSql = sql;
            return (T) queryOneResult;
        }

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

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

        @Override
        public <T> int[][] batchUpdate(String sql,
                                       List<T> items,
                                       int batchSize,
                                       ParameterizedPreparedStatementSetter<T> setter) {
            return new int[0][];
        }

        @Override
        public <T> AnalyticalDBPageResult<T> page(String countSql,
                                                  Object[] countArgs,
                                                  String dataSql,
                                                  Object[] dataArgs,
                                                  AnalyticalDBPageRequest pageRequest,
                                                  RowMapper<T> rowMapper) {
            this.lastPageCountSql = countSql;
            this.lastPageCountArgs = countArgs;
            this.lastPageDataSql = dataSql;
            this.lastPageDataArgs = dataArgs;
            return new AnalyticalDBPageResult<>();
        }
    }
}
