package cn.novalon.gym.manage.datacount.service.impl;

import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.datacount.dao.DataStatisticsDao;
import cn.novalon.gym.manage.datacount.domain.*;
import cn.novalon.gym.manage.datacount.service.IDataStatisticsService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.io.ByteArrayOutputStream;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据统计服务实现类
 *
 * @author system
 * @date 2026-06-09
 */
@Service
public class DataStatisticsServiceImpl implements IDataStatisticsService {

    private static final Logger log = LoggerFactory.getLogger(DataStatisticsServiceImpl.class);

    private static final String CACHE_KEY_PREFIX = "datacount:statistics:";
    private static final long CACHE_EXPIRE_SECONDS = 3600; // 1小时

    @Autowired
    private DataStatisticsDao dataStatisticsDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private ObjectMapper objectMapper;

    @Value("${datacount.cache.expire-seconds:3600}")
    private long cacheExpireSeconds = 3600;

    @Override
    public Mono<MemberStatistics> getMemberStatistics(StatisticsQuery query) {
        LocalDateTime startTime = getStartTime(query);
        LocalDateTime endTime = getEndTime(query);

        Mono<Long> newMembersMono = dataStatisticsDao.countNewMembers(startTime, endTime);
        Mono<Long> totalMembersMono = dataStatisticsDao.countTotalMembers();
        Mono<Long> signInMembersMono = dataStatisticsDao.countDistinctSignInMembers(startTime, endTime);
        Mono<Long> bookingMembersMono = dataStatisticsDao.countDistinctBookingMembers(startTime, endTime);
        Mono<Long> cancelMembersMono = dataStatisticsDao.countDistinctCancelMembers(startTime, endTime);

        return Mono.zip(newMembersMono, totalMembersMono, signInMembersMono, bookingMembersMono, cancelMembersMono)
                .map(tuple -> {
                    long newMembers = tuple.getT1() != null ? tuple.getT1() : 0L;
                    long totalMembers = tuple.getT2() != null ? tuple.getT2() : 0L;
                    long signInMembers = tuple.getT3() != null ? tuple.getT3() : 0L;
                    long bookingMembers = tuple.getT4() != null ? tuple.getT4() : 0L;
                    long cancelMembers = tuple.getT5() != null ? tuple.getT5() : 0L;

                    // 活跃会员数 = 有签到的 + 有预约的（去重后大概值）
                    long activeMembers = signInMembers + bookingMembers;

                    return MemberStatistics.builder()
                            .statDate(startTime.toLocalDate().toString())
                            .newMembers(newMembers)
                            .activeMembers(activeMembers)
                            .totalMembers(totalMembers)
                            .signInMembers(signInMembers)
                            .bookingMembers(bookingMembers)
                            .cancelBookingMembers(cancelMembers)
                            .build();
                });
    }

    @Override
    public Mono<BookingStatistics> getBookingStatistics(StatisticsQuery query) {
        LocalDateTime startTime = getStartTime(query);
        LocalDateTime endTime = getEndTime(query);

        Mono<Long> totalMono = dataStatisticsDao.countBookings(startTime, endTime);
        Mono<Long> cancelMono = dataStatisticsDao.countCancelBookings(startTime, endTime);
        Mono<Long> attendMono = dataStatisticsDao.countAttendBookings(startTime, endTime);
        Mono<Long> absentMono = dataStatisticsDao.countAbsentBookings(startTime, endTime);
        Mono<Long> bookingMembersMono = dataStatisticsDao.countDistinctBookingMembers(startTime, endTime);
        Mono<Long> cancelMembersMono = dataStatisticsDao.countDistinctCancelMembers(startTime, endTime);

        return Mono.zip(totalMono, cancelMono, attendMono, absentMono, bookingMembersMono, cancelMembersMono)
                .map(tuple -> {
                    long total = tuple.getT1() != null ? tuple.getT1() : 0L;
                    long cancel = tuple.getT2() != null ? tuple.getT2() : 0L;
                    long attend = tuple.getT3() != null ? tuple.getT3() : 0L;
                    long absent = tuple.getT4() != null ? tuple.getT4() : 0L;
                    long bookingMembers = tuple.getT5() != null ? tuple.getT5() : 0L;
                    long cancelMembers = tuple.getT6() != null ? tuple.getT6() : 0L;

                    double attendanceRate = total > 0 ? (double) attend / total * 100 : 0;
                    double cancelRate = total > 0 ? (double) cancel / total * 100 : 0;

                    return BookingStatistics.builder()
                            .statDate(startTime.toLocalDate().toString())
                            .newBookings(total)
                            .cancelBookings(cancel)
                            .attendBookings(attend)
                            .absentBookings(absent)
                            .attendanceRate(Math.round(attendanceRate * 100.0) / 100.0)
                            .cancelRate(Math.round(cancelRate * 100.0) / 100.0)
                            .bookingMembers(bookingMembers)
                            .cancelMembers(cancelMembers)
                            .build();
                });
    }

    @Override
    public Mono<SignInStatistics> getSignInStatistics(StatisticsQuery query) {
        LocalDateTime startTime = getStartTime(query);
        LocalDateTime endTime = getEndTime(query);

        Mono<Long> totalMono = dataStatisticsDao.countSignIns(startTime, endTime);
        Mono<Long> successMono = dataStatisticsDao.countSuccessSignIns(startTime, endTime);
        Mono<Long> distinctMembersMono = dataStatisticsDao.countDistinctSignInMembers(startTime, endTime);

        return Mono.zip(totalMono, successMono, distinctMembersMono)
                .flatMap(tuple -> {
                    long total = tuple.getT1() != null ? tuple.getT1() : 0L;
                    long success = tuple.getT2() != null ? tuple.getT2() : 0L;
                    long distinctMembers = tuple.getT3() != null ? tuple.getT3() : 0L;

                    double successRate = total > 0 ? (double) success / total * 100 : 0;

                    return dataStatisticsDao.countSignInsByType(startTime, endTime)
                            .collectMap(
                                    DataStatisticsDao.SignInTypeCount::getType,
                                    DataStatisticsDao.SignInTypeCount::getCount
                            )
                            .defaultIfEmpty(new HashMap<>())
                            .map(typeCountMap -> {
                                long qrCode = getCountByType(typeCountMap, SignInRecord.SignInType.QR_CODE);
                                long manual = getCountByType(typeCountMap, SignInRecord.SignInType.MANUAL);
                                long face = getCountByType(typeCountMap, SignInRecord.SignInType.FACE);

                                return SignInStatistics.builder()
                                        .statDate(startTime.toLocalDate().toString())
                                        .totalSignIns(total)
                                        .successSignIns(success)
                                        .failedSignIns(total - success)
                                        .successRate(Math.round(successRate * 100.0) / 100.0)
                                        .signInMembers(distinctMembers)
                                        .qrCodeSignIns(qrCode)
                                        .manualSignIns(manual)
                                        .faceSignIns(face)
                                        .build();
                            });
                });
    }

    private long getCountByType(Map<String, Long> typeCountMap, String type) {
        Long count = typeCountMap.get(type);
        return count != null ? count : 0L;
    }

    @Override
    public Mono<StatisticsSummary> getStatisticsSummary(StatisticsQuery query) {
        Mono<MemberStatistics> memberStatsMono = getMemberStatistics(query);
        Mono<BookingStatistics> bookingStatsMono = getBookingStatistics(query);
        Mono<SignInStatistics> signInStatsMono = getSignInStatistics(query);

        return Mono.zip(memberStatsMono, bookingStatsMono, signInStatsMono)
                .map(tuple -> StatisticsSummary.builder()
                        .statDate(LocalDateTime.now().toLocalDate().toString())
                        .memberStatistics(tuple.getT1())
                        .bookingStatistics(tuple.getT2())
                        .signInStatistics(tuple.getT3())
                        .generatedAt(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
                        .build());
    }

    @Override
    public reactor.core.publisher.Flux<DataStatistics> queryHistoricalStatistics(StatisticsQuery query) {
        // 历史统计数据查询（从Redis缓存中获取）
        String cacheKey = buildCacheKey(query);
        return redisUtil.get(cacheKey, String.class)
                .flatMapMany(json -> {
                    try {
                        java.util.List<DataStatistics> stats = objectMapper.readValue(json,
                                objectMapper.getTypeFactory().constructCollectionType(java.util.List.class, DataStatistics.class));
                        return reactor.core.publisher.Flux.fromIterable(stats);
                    } catch (Exception e) {
                        log.error("Failed to parse historical statistics from cache", e);
                        return reactor.core.publisher.Flux.empty();
                    }
                })
                .switchIfEmpty(reactor.core.publisher.Flux.empty());
    }

    @Override
    public Mono<Void> executeDailyStatistics(LocalDate statDate) {
        log.info("Executing daily statistics for date: {}", statDate);

        LocalDateTime dayStart = statDate.atStartOfDay();
        LocalDateTime dayEnd = statDate.plusDays(1).atStartOfDay();

        StatisticsQuery query = StatisticsQuery.builder()
                .startTime(dayStart)
                .endTime(dayEnd)
                .build();

        Mono<MemberStatistics> memberStatsMono = getMemberStatistics(query);
        Mono<BookingStatistics> bookingStatsMono = getBookingStatistics(query);
        Mono<SignInStatistics> signInStatsMono = getSignInStatistics(query);

        return Mono.zip(memberStatsMono, bookingStatsMono, signInStatsMono)
                .flatMap(tuple -> {
                    MemberStatistics memberStats = tuple.getT1();
                    BookingStatistics bookingStats = tuple.getT2();
                    SignInStatistics signInStats = tuple.getT3();

                    String dateStr = statDate.toString();
                    String memberKey = CACHE_KEY_PREFIX + "member:" + dateStr;
                    String bookingKey = CACHE_KEY_PREFIX + "booking:" + dateStr;
                    String signInKey = CACHE_KEY_PREFIX + "signin:" + dateStr;

                    try {
                        String memberJson = objectMapper.writeValueAsString(memberStats);
                        String bookingJson = objectMapper.writeValueAsString(bookingStats);
                        String signInJson = objectMapper.writeValueAsString(signInStats);

                        return reactor.core.publisher.Flux.merge(
                                redisUtil.setWithExpire(memberKey, memberJson, Duration.ofDays(30).getSeconds()),
                                redisUtil.setWithExpire(bookingKey, bookingJson, Duration.ofDays(30).getSeconds()),
                                redisUtil.setWithExpire(signInKey, signInJson, Duration.ofDays(30).getSeconds())
                        ).then();
                    } catch (Exception e) {
                        log.error("Failed to serialize statistics data", e);
                        return Mono.empty();
                    }
                })
                .then()
                .doOnSuccess(v -> log.info("Daily statistics completed for date: {}", statDate))
                .doOnError(e -> log.error("Failed to execute daily statistics for date: {}", statDate, e));
    }

    @Override
    public Mono<byte[]> exportStatistics(StatisticsQuery query) {
        return getStatisticsSummary(query)
                .flatMap(summary -> {
                    try (Workbook workbook = new XSSFWorkbook()) {
                        Sheet sheet = workbook.createSheet("数据统计报表");

                        CellStyle headerStyle = workbook.createCellStyle();
                        Font headerFont = workbook.createFont();
                        headerFont.setBold(true);
                        headerStyle.setFont(headerFont);

                        createMainSheet(sheet, summary, headerStyle);

                        Sheet memberSheet = workbook.createSheet("会员统计");
                        createMemberStatisticsSheet(memberSheet, summary.getMemberStatistics(), headerStyle);

                        Sheet bookingSheet = workbook.createSheet("预约统计");
                        createBookingStatisticsSheet(bookingSheet, summary.getBookingStatistics(), headerStyle);

                        Sheet signInSheet = workbook.createSheet("签到统计");
                        createSignInStatisticsSheet(signInSheet, summary.getSignInStatistics(), headerStyle);

                        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                        workbook.write(outputStream);
                        return Mono.just(outputStream.toByteArray());
                    } catch (Exception e) {
                        log.error("Failed to export statistics", e);
                        return Mono.error(e);
                    }
                });
    }

    private void createMainSheet(Sheet sheet, StatisticsSummary summary, CellStyle headerStyle) {
        int rowNum = 0;
        Row row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("统计项");
        row.createCell(1).setCellValue("数值");

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("统计日期");
        row.createCell(1).setCellValue(summary.getStatDate());

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("新增会员数");
        row.createCell(1).setCellValue(summary.getMemberStatistics().getNewMembers());

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("活跃会员数");
        row.createCell(1).setCellValue(summary.getMemberStatistics().getActiveMembers());

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("累计会员总数");
        row.createCell(1).setCellValue(summary.getMemberStatistics().getTotalMembers());

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("新增预约数");
        row.createCell(1).setCellValue(summary.getBookingStatistics().getNewBookings());

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("预约出席率");
        row.createCell(1).setCellValue(summary.getBookingStatistics().getAttendanceRate() + "%");

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("签到总次数");
        row.createCell(1).setCellValue(summary.getSignInStatistics().getTotalSignIns());

        row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("签到成功率");
        row.createCell(1).setCellValue(summary.getSignInStatistics().getSuccessRate() + "%");

        sheet.autoSizeColumn(0);
        sheet.autoSizeColumn(1);
    }

    private void createMemberStatisticsSheet(Sheet sheet, MemberStatistics stats, CellStyle headerStyle) {
        int rowNum = 0;
        Row row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("统计项");
        row.createCell(1).setCellValue("数值");

        String[][] data = {
                {"统计日期", stats.getStatDate()},
                {"新增会员数", String.valueOf(stats.getNewMembers())},
                {"活跃会员数", String.valueOf(stats.getActiveMembers())},
                {"累计会员总数", String.valueOf(stats.getTotalMembers())},
                {"今日签到会员数", String.valueOf(stats.getSignInMembers())},
                {"今日预约会员数", String.valueOf(stats.getBookingMembers())},
                {"今日取消预约会员数", String.valueOf(stats.getCancelBookingMembers())}
        };

        for (String[] rowData : data) {
            row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(rowData[0]);
            row.createCell(1).setCellValue(rowData[1]);
        }

        sheet.autoSizeColumn(0);
        sheet.autoSizeColumn(1);
    }

    private void createBookingStatisticsSheet(Sheet sheet, BookingStatistics stats, CellStyle headerStyle) {
        int rowNum = 0;
        Row row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("统计项");
        row.createCell(1).setCellValue("数值");

        String[][] data = {
                {"统计日期", stats.getStatDate()},
                {"新增预约数", String.valueOf(stats.getNewBookings())},
                {"取消预约数", String.valueOf(stats.getCancelBookings())},
                {"出席预约数", String.valueOf(stats.getAttendBookings())},
                {"缺席预约数", String.valueOf(stats.getAbsentBookings())},
                {"预约出席率", stats.getAttendanceRate() + "%"},
                {"取消率", stats.getCancelRate() + "%"},
                {"预约人数", String.valueOf(stats.getBookingMembers())},
                {"取消人数", String.valueOf(stats.getCancelMembers())}
        };

        for (String[] rowData : data) {
            row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(rowData[0]);
            row.createCell(1).setCellValue(rowData[1]);
        }

        sheet.autoSizeColumn(0);
        sheet.autoSizeColumn(1);
    }

    private void createSignInStatisticsSheet(Sheet sheet, SignInStatistics stats, CellStyle headerStyle) {
        int rowNum = 0;
        Row row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue("统计项");
        row.createCell(1).setCellValue("数值");

        String[][] data = {
                {"统计日期", stats.getStatDate()},
                {"签到总次数", String.valueOf(stats.getTotalSignIns())},
                {"成功签到次数", String.valueOf(stats.getSuccessSignIns())},
                {"失败签到次数", String.valueOf(stats.getFailedSignIns())},
                {"签到成功率", stats.getSuccessRate() + "%"},
                {"签到人数", String.valueOf(stats.getSignInMembers())},
                {"扫码签到次数", String.valueOf(stats.getQrCodeSignIns())},
                {"手动签到次数", String.valueOf(stats.getManualSignIns())},
                {"人脸识别签到次数", String.valueOf(stats.getFaceSignIns())}
        };

        for (String[] rowData : data) {
            row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(rowData[0]);
            row.createCell(1).setCellValue(rowData[1]);
        }

        sheet.autoSizeColumn(0);
        sheet.autoSizeColumn(1);
    }

    @Override
    public Mono<StatisticsSummary> getStatisticsSummaryWithCache(StatisticsQuery query) {
        String cacheKey = buildCacheKey(query);

        return redisUtil.get(cacheKey, StatisticsSummary.class)
                .switchIfEmpty(
                        getStatisticsSummary(query)
                                .flatMap(summary -> {
                                    try {
                                        String json = objectMapper.writeValueAsString(summary);
                                        return redisUtil.setWithExpire(cacheKey, json, cacheExpireSeconds)
                                                .thenReturn(summary);
                                    } catch (Exception e) {
                                        log.error("Failed to serialize statistics summary", e);
                                        return Mono.just(summary);
                                    }
                                })
                );
    }

    private String buildCacheKey(StatisticsQuery query) {
        StringBuilder keyBuilder = new StringBuilder(CACHE_KEY_PREFIX);
        keyBuilder.append("summary:");

        if (query.getStartTime() != null) {
            keyBuilder.append(query.getStartTime().toLocalDate().toString());
        }
        if (query.getEndTime() != null) {
            keyBuilder.append("_").append(query.getEndTime().toLocalDate().toString());
        }
        if (query.getStatType() != null) {
            keyBuilder.append("_").append(query.getStatType());
        }
        if (query.getPeriodType() != null) {
            keyBuilder.append("_").append(query.getPeriodType());
        }

        return keyBuilder.toString();
    }

    private LocalDateTime getStartTime(StatisticsQuery query) {
        if (query.getStartTime() != null) {
            return query.getStartTime();
        }

        LocalDate today = LocalDate.now();
        String periodType = query.getPeriodType();

        if (DataStatistics.PeriodType.WEEK.equals(periodType)) {
            // 周统计：本周一
            return today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).atStartOfDay();
        } else if (DataStatistics.PeriodType.MONTH.equals(periodType)) {
            // 月统计：本月第一天
            return today.withDayOfMonth(1).atStartOfDay();
        } else {
            // 日统计：当天零点
            return today.atStartOfDay();
        }
    }

    private LocalDateTime getEndTime(StatisticsQuery query) {
        if (query.getEndTime() != null) {
            return query.getEndTime();
        }

        LocalDate today = LocalDate.now();
        String periodType = query.getPeriodType();

        if (DataStatistics.PeriodType.WEEK.equals(periodType)) {
            // 周统计：本周日 23:59:59
            return today.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)).atTime(23, 59, 59);
        } else if (DataStatistics.PeriodType.MONTH.equals(periodType)) {
            // 月统计：本月最后一天 23:59:59
            return today.with(TemporalAdjusters.lastDayOfMonth()).atTime(23, 59, 59);
        } else {
            // 日统计：当前时间
            return LocalDateTime.now();
        }
    }

    /**
     * 根据周期类型调整时间范围
     * 用于定时任务中的周期统计
     */
    private LocalDateTime[] adjustTimeRangeByPeriod(LocalDate date, String periodType) {
        LocalDateTime startTime;
        LocalDateTime endTime;

        if (DataStatistics.PeriodType.WEEK.equals(periodType)) {
            startTime = date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).atStartOfDay();
            endTime = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)).atTime(23, 59, 59);
        } else if (DataStatistics.PeriodType.MONTH.equals(periodType)) {
            startTime = date.withDayOfMonth(1).atStartOfDay();
            endTime = date.with(TemporalAdjusters.lastDayOfMonth()).atTime(23, 59, 59);
        } else {
            startTime = date.atStartOfDay();
            endTime = date.plusDays(1).atStartOfDay();
        }

        return new LocalDateTime[]{startTime, endTime};
    }
}
