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

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.novalon.gym.manage.checkIn.config.QRCodeConfig;
import cn.novalon.gym.manage.checkIn.constant.QRRedisKey;
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
import cn.novalon.gym.manage.checkIn.repository.SignInRecordRepository;
import cn.novalon.gym.manage.checkIn.service.ICheckInService;
import cn.novalon.gym.manage.checkIn.vo.QRCodeVo;
import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
import cn.novalon.gym.manage.checkIn.websocket.MyWebSocketHandler;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.enums.MemberCardType;
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class CheckServiceImpl implements ICheckInService {

    private final QRCodeConfig qrCodeConfig;
    private final RedisUtil redisUtil;
    private final MemberCardRecordRepository memberCardRecordRepository;
    private final MemberCardRepository memberCardRepository;
    private final SignInRecordRepository signInRecordRepository;
    private final IGroupCourseBookingService groupCourseBookingService;

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public Mono<QRCodeVo> getQRCode(Long memberId) {
        log.info("开始查询会员信息, memberId: {}", memberId);
        
        return findValidMemberCard(memberId)
                .flatMap(cardRecord -> {
                    log.info("会员信息查询完成, memberCardRecordId: {}", cardRecord.getMemberCardRecordId());
                    
                    log.info("开始生成二维码");
                    String qrContent = QRRedisKey.generateQrcodeContent();
                    Map<String, Object> redisMap = new HashMap<>();
                    redisMap.put("qrContent", qrContent);
                    redisMap.put("isUsed", false);
                    redisMap.put("memberId", memberId);
                    redisMap.put("memberCardRecordId", cardRecord.getMemberCardRecordId());

                    return redisUtil.setWithExpire(
                                    RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(),
                                    redisMap,
                                    getSecondsUntilEndOfDay()
                            )
                            .then(Mono.fromSupplier(() -> {
                                String qrCodeBase64 = QrCodeUtil.generateAsBase64(qrContent,
                                        BeanUtil.copyProperties(qrCodeConfig, QrConfig.class), "png");
                                return new QRCodeVo(qrCodeBase64, false, qrContent, qrCodeConfig.getWidth(), qrCodeConfig.getHeight(), LocalDate.now());
                            }));
                })
                .switchIfEmpty(Mono.error(new RuntimeException("该会员没有可用的会员卡")));
    }

    @Override
    public Mono<String> checkIn(Long memberId, String qrContent) {
        String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();

        // 先检查当天是否已经签到（从数据库查询，作为重复签到的额外保障）
        return checkTodayAlreadySignedIn(memberId)
                .flatMap(existingRecord -> {
                    String checkInTime = existingRecord.getSignInTime().format(DATE_FORMATTER);
                    log.error("重复签到, memberId: {}", memberId);
                    MyWebSocketHandler.sendFailure(qrContent, "您已经在" + checkInTime + "完成签到，请勿重复签到");
                    return Mono.error(new RuntimeException("您已经在" + checkInTime + "完成签到，请勿重复签到"));
                })
                .then(Mono.defer(() -> redisUtil.get(key)))
                .flatMap(cachedObj -> {
                    if (cachedObj != null) {
                        Map<String, Object> map;
                        if (cachedObj instanceof Map) {
                            map = (Map<String, Object>) cachedObj;
                        } else if (cachedObj instanceof String) {
                            map = JSONUtil.parseObj((String) cachedObj);
                        } else {
                            MyWebSocketHandler.sendFailure(qrContent, "二维码数据格式错误");
                            return Mono.error(new RuntimeException("二维码数据格式错误"));
                        }
                        if (map.get("qrContent").equals(qrContent)) {
                            if ((boolean) map.get("isUsed")) {
                                String checkInTime = String.valueOf(map.get("checkInTime"));
                                log.error("重复签到(缓存), memberId: {}", memberId);
                                MyWebSocketHandler.sendFailure(qrContent, "您已经在" + checkInTime + "完成签到，请勿重复签到");
                                return Mono.error(new RuntimeException("您已经在" + checkInTime + "完成签到，请勿重复签到"));
                            }
                            log.info("二维码匹配成功，memberId: {}", memberId);

                            Long memberCardRecordId = ((Number) map.get("memberCardRecordId")).longValue();

                            return processCheckIn(memberId, memberCardRecordId, map, qrContent);
                        } else {
                            MyWebSocketHandler.sendFailure(qrContent, "二维码无效");
                            return Mono.error(new RuntimeException("二维码无效"));
                        }
                    }
                    MyWebSocketHandler.sendFailure(qrContent, "二维码已过期或不存在");
                    return Mono.error(new RuntimeException("二维码已过期或不存在"));
                });
    }

    /**
     * 检查会员当天是否已经签到
     * @param memberId 会员ID
     * @return 如果已签到返回签到记录，否则返回空
     */
    private Mono<SignInRecord> checkTodayAlreadySignedIn(Long memberId) {
        LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
        LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX);
        
        return signInRecordRepository.findByMemberIdAndDate(memberId, startOfDay, endOfDay);
    }

    /**
     * 处理签到逻辑
     */
    private Mono<String> processCheckIn(Long memberId, Long memberCardRecordId, Map<String, Object> redisMap, String qrContent) {
        LocalDateTime now = LocalDateTime.now();
        
        // 发送实时进度通知
        MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_CARD", "正在验证会员卡...");
        
        return memberCardRecordRepository.findById(memberCardRecordId)
                .switchIfEmpty(Mono.defer(() -> {
                    MyWebSocketHandler.sendFailure(qrContent, "会员卡记录不存在");
                    return Mono.error(new RuntimeException("会员卡记录不存在"));
                }))
                .flatMap(cardRecord -> {
                    if (!"ACTIVE".equals(cardRecord.getStatus().name())) {
                        MyWebSocketHandler.sendFailure(qrContent, "会员卡状态不正确");
                        return Mono.error(new RuntimeException("会员卡状态不正确"));
                    }
                    
                    if (cardRecord.getExpireTime() != null && cardRecord.getExpireTime().isBefore(now)) {
                        MyWebSocketHandler.sendFailure(qrContent, "会员卡已过期");
                        return Mono.error(new RuntimeException("会员卡已过期"));
                    }

                    // 发送实时进度通知
                    MyWebSocketHandler.sendProgress(qrContent, "VALIDATE_BOOKING", "会员卡验证通过，正在检查预约信息...");

                    // 检查是否有需要签到的团课预约
                    return validateBooking(memberId, now)
                            .then(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(cardRecord.getMemberCardId())
                                    .switchIfEmpty(Mono.defer(() -> {
                                        MyWebSocketHandler.sendFailure(qrContent, "会员卡类型不存在");
                                        return Mono.error(new RuntimeException("会员卡类型不存在"));
                                    }))
                                    .flatMap(card -> {
                                        // 发送实时进度通知
                                        MyWebSocketHandler.sendProgress(qrContent, "DEDUCT_USAGE", "正在扣减会员卡次数...");
                                        
                                        return deductCardUsage(cardRecord, card)
                                                .flatMap(updatedRecord -> {
                                                    redisMap.put("isUsed", true);
                                                    redisMap.put("checkInTime", now.format(DATE_FORMATTER));
                                                    
                                                    return saveSignInRecord(memberId, cardRecord.getMemberCardRecordId(), card.getMemberCardId())
                                                            .then(redisUtil.set(RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now(), redisMap))
                                                            .then(Mono.defer(() -> {
                                                                String successMsg = buildSuccessResponse(now);
                                                                MyWebSocketHandler.sendSuccess(qrContent, memberId, now.format(DATE_FORMATTER));
                                                                log.info("签到成功, memberId: {}, cardRecordId: {}", memberId, memberCardRecordId);
                                                                return Mono.just(successMsg);
                                                            }));
                                                });
                                    }));
                });
    }

    /**
     * 验证预约信息
     */
    private Mono<Void> validateBooking(Long memberId, LocalDateTime now) {
        return groupCourseBookingService.getBookingsByMemberId(memberId)
                .filter(booking -> {
                    String status = booking.getStatus();
                    LocalDateTime startTime = booking.getCourseStartTime();
                    return "0".equals(status) && 
                           startTime != null && 
                           startTime.toLocalDate().equals(now.toLocalDate()) &&
                           !startTime.isBefore(now.minusMinutes(30));
                })
                .collectList()
                .flatMap(bookings -> {
                    if (bookings.isEmpty()) {
                        return Mono.empty();
                    }
                    boolean hasValidBooking = bookings.stream()
                            .anyMatch(b -> {
                                LocalDateTime startTime = b.getCourseStartTime();
                                return startTime != null && 
                                       !startTime.isBefore(now.minusMinutes(30)) && 
                                       !startTime.isAfter(now.plusMinutes(30));
                            });
                    if (hasValidBooking) {
                        log.info("会员{}有有效的团课预约", memberId);
                    } else {
                        log.warn("会员{}有预约但不在签到时间范围内", memberId);
                    }
                    return Mono.empty();
                });
    }

    /**
     * 扣减会员卡使用次数/金额
     */
    private Mono<MemberCardRecord> deductCardUsage(MemberCardRecord record, MemberCard card) {
        MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());
        LocalDateTime now = LocalDateTime.now();

        switch (cardType) {
            case TIME_CARD:
                if (record.getExpireTime() != null && record.getExpireTime().isBefore(now)) {
                    return Mono.error(new RuntimeException("时长卡已过期"));
                }
                return Mono.just(record);
            case COUNT_CARD:
                int currentTimes = record.getRemainingTimes() != null ? record.getRemainingTimes() : 0;
                if (currentTimes < 1) {
                    return Mono.error(new RuntimeException("次卡剩余次数不足"));
                }
                record.setRemainingTimes(currentTimes - 1);
                if (record.getRemainingTimes() == 0) {
                    record.setStatus(cn.novalon.gym.manage.member.enums.MemberCardRecordStatus.USED_UP);
                }
                return memberCardRecordRepository.save(record);
            case STORED_VALUE_CARD:
                double currentAmount = record.getRemainingAmount() != null ? record.getRemainingAmount() : 0.0;
                if (currentAmount < 0.01) {
                    return Mono.error(new RuntimeException("储值卡余额不足"));
                }
                record.setRemainingAmount(Math.max(0, currentAmount - 1));
                if (record.getRemainingAmount() <= 0) {
                    record.setStatus(cn.novalon.gym.manage.member.enums.MemberCardRecordStatus.USED_UP);
                }
                return memberCardRecordRepository.save(record);
            default:
                return Mono.error(new RuntimeException("不支持的会员卡类型"));
        }
    }

    /**
     * 保存签到记录
     */
    private Mono<Void> saveSignInRecord(Long memberId, Long memberCardRecordId, Long memberCardId) {
        SignInRecord record = SignInRecord.builder()
                .memberId(memberId)
                .memberCardId(memberCardId)
                .signInTime(LocalDateTime.now())
                .signInType(SignInRecord.SignInType.QR_CODE)
                .signInStatus(SignInRecord.SignInStatus.SUCCESS)
                .source(SignInRecord.Source.MINI_PROGRAM)
                .isDelete(false)
                .build();

        return signInRecordRepository.save(record).then();
    }

    /**
     * 构建成功响应
     */
    private String buildSuccessResponse(LocalDateTime dateTime) {
        Map<String, Object> res = new HashMap<>();
        res.put("message", "签到成功");
        res.put("dateTime", dateTime.format(DATE_FORMATTER));
        return JSONUtil.toJsonStr(res);
    }

    /**
     * 查找会员的有效会员卡（优先选择有效期最早到期的）
     */
    private Mono<MemberCardRecord> findValidMemberCard(Long memberId) {
        return memberCardRecordRepository.findActiveCardsByMemberId(memberId)
                .filter(record -> {
                    LocalDateTime expireTime = record.getExpireTime();
                    return expireTime == null || expireTime.isAfter(LocalDateTime.now());
                })
                .sort((r1, r2) -> {
                    LocalDateTime e1 = r1.getExpireTime();
                    LocalDateTime e2 = r2.getExpireTime();
                    if (e1 == null && e2 == null) return 0;
                    if (e1 == null) return 1;
                    if (e2 == null) return -1;
                    return e1.compareTo(e2);
                })
                .next();
    }

    // ==================== 签到记录管理功能 ====================

    @Override
    public Flux<SignInRecordVO> getSignInRecords(Long memberId, LocalDate startTime, LocalDate endTime) {
        LocalDateTime start = startTime.atStartOfDay();
        LocalDateTime end = endTime.atTime(LocalTime.MAX);
        
        return signInRecordRepository.findByMemberIdAndTimeRange(memberId, start, end)
                .map(this::convertToVO);
    }

    @Override
    public Mono<SignInRecordVO> getSignInRecordById(Long id) {
        return signInRecordRepository.findById(id)
                .map(this::convertToVO);
    }

    @Override
    public Mono<SignInStatsVO> getSignInStats(Long memberId, LocalDate startTime, LocalDate endTime) {
        LocalDateTime start = startTime.atStartOfDay();
        LocalDateTime end = endTime.atTime(LocalTime.MAX);
        
        return Mono.zip(
                (Object[] results) -> {
                    Long total = (Long) results[0];
                    Long success = (Long) results[1];
                    LocalDateTime first = (LocalDateTime) results[2];
                    LocalDateTime last = (LocalDateTime) results[3];
                    SignInStatsVO stats = new SignInStatsVO();
                    stats.setTotalCount(total);
                    stats.setSuccessCount(success);
                    stats.setStartDate(startTime);
                    stats.setEndDate(endTime);
                    stats.setFirstSignInTime(first);
                    stats.setLastSignInTime(last);
                    stats.setSuccessRate(total > 0 ? (double) success / total * 100.0 : 0.0);
                    return stats;
                },
                signInRecordRepository.countByMemberIdAndTimeRange(memberId, start, end),
                signInRecordRepository.countSuccessByMemberIdAndTimeRange(memberId, start, end),
                signInRecordRepository.getFirstSignInTime(memberId, start, end),
                signInRecordRepository.getLastSignInTime(memberId, start, end)
        );
    }

    @Override
    public Mono<byte[]> exportSignInRecords(Long memberId, LocalDate startTime, LocalDate endTime) {
        LocalDateTime start = startTime.atStartOfDay();
        LocalDateTime end = endTime.atTime(LocalTime.MAX);
        
        return signInRecordRepository.findByMemberIdAndTimeRange(memberId, start, end)
                .map(record -> {
                    String status = "SUCCESS".equals(record.getSignInStatus()) ? "成功" : "失败";
                    String type = "QR_CODE".equals(record.getSignInType()) ? "扫码签到" : 
                                  "MANUAL".equals(record.getSignInType()) ? "手动签到" : "人脸识别";
                    return String.join(",",
                            record.getId().toString(),
                            record.getMemberId().toString(),
                            record.getMemberCardId() != null ? record.getMemberCardId().toString() : "",
                            record.getSignInTime() != null ? record.getSignInTime().format(DATE_FORMATTER) : "",
                            type,
                            status,
                            record.getFailReason() != null ? record.getFailReason() : ""
                    );
                })
                .collectList()
                .map(rows -> {
                    List<String> csvLines = new java.util.ArrayList<>();
                    csvLines.add("签到记录ID,会员ID,会员卡ID,签到时间,签到方式,签到状态,失败原因");
                    csvLines.addAll(rows);
                    return String.join("\n", csvLines).getBytes(java.nio.charset.StandardCharsets.UTF_8);
                });
    }

    @Override
    public Mono<SignInStatsVO> getDailySignInStats(LocalDate date) {
        LocalDateTime start = date.atStartOfDay();
        LocalDateTime end = date.atTime(LocalTime.MAX);
        
        return Mono.zip(
                (Object[] results) -> {
                    Long total = (Long) results[0];
                    Long success = (Long) results[1];
                    Long members = (Long) results[2];
                    SignInStatsVO stats = new SignInStatsVO();
                    stats.setTotalCount(total);
                    stats.setSuccessCount(success);
                    stats.setStartDate(date);
                    stats.setEndDate(date);
                    stats.setUniqueMemberCount(members);
                    stats.setSuccessRate(total > 0 ? (double) success / total * 100.0 : 0.0);
                    return stats;
                },
                signInRecordRepository.countByTimeRange(start, end),
                signInRecordRepository.countSuccessByTimeRange(start, end),
                signInRecordRepository.countDistinctMembersByTimeRange(start, end)
        );
    }

    /**
     * 转换实体到VO
     */
    private SignInRecordVO convertToVO(SignInRecord record) {
        SignInRecordVO vo = new SignInRecordVO();
        vo.setId(record.getId());
        vo.setMemberId(record.getMemberId());
        vo.setMemberCardId(record.getMemberCardId());
        vo.setSignInTime(record.getSignInTime());
        vo.setSignInType(record.getSignInType());
        vo.setSignInStatus(record.getSignInStatus());
        vo.setFailReason(record.getFailReason());
        vo.setSource(record.getSource());
        vo.setCreatedAt(record.getCreatedAt());
        return vo;
    }

    private long getSecondsUntilEndOfDay() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59);
        if (now.isAfter(endOfDay)) return 1;
        return ChronoUnit.SECONDS.between(now, endOfDay);
    }
}