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

import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
import cn.novalon.gym.manage.groupcourse.event.BookingReminderEventPublisher;
import cn.novalon.gym.manage.groupcourse.handler.BookingSagaHandler;
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

/**
 * 团课预约服务实现类
 * 
 * 业务规则：
 * - 预约需在课程开始前至少30分钟
 * - 取消预约需在课程开始前至少2小时
 * - 每节课最多20人
 * - 预约成功后发送提醒
 * - 预约成功后扣减权益
 * 
 * 技术要点：
 * - 使用Redis缓存团课信息
 * - 使用分布式锁防止预约冲突
 * - 使用响应式编程实现高并发预约
 * 
 * @author 张翔
 * @date 2026-06-01
 */
@Service
public class GroupCourseBookingService implements IGroupCourseBookingService {

    private static final Logger logger = LoggerFactory.getLogger(GroupCourseBookingService.class);

    private final IGroupCourseBookingRepository bookingRepository;
    private final IGroupCourseRepository courseRepository;
    private final GroupCourseRedisService redisService;
    private final BookingReminderEventPublisher bookingReminderEventPublisher;
    private final BookingSagaHandler bookingSagaHandler;

    // 预约提前时间限制（分钟）
    private static final long BOOKING_MIN_ADVANCE_MINUTES = 30;
    // 取消预约提前时间限制（小时）
    private static final long CANCEL_MIN_ADVANCE_HOURS = 2;

    public GroupCourseBookingService(IGroupCourseBookingRepository bookingRepository,
                                     IGroupCourseRepository courseRepository,
                                     GroupCourseRedisService redisService,
                                     BookingReminderEventPublisher bookingReminderEventPublisher,
                                     BookingSagaHandler bookingSagaHandler) {
        this.bookingRepository = bookingRepository;
        this.courseRepository = courseRepository;
        this.redisService = redisService;
        this.bookingReminderEventPublisher = bookingReminderEventPublisher;
        this.bookingSagaHandler = bookingSagaHandler;
    }

    @Override
    public Mono<GroupCourseBooking> bookCourse(Long courseId, Long memberId, Long memberCardRecordId) {
        logger.info("开始预约团课：courseId={}, memberId={}, memberCardRecordId={}", courseId, memberId, memberCardRecordId);

        // 生成唯一请求ID用于分布式锁
        String requestId = UUID.randomUUID().toString();

        // 1. 获取分布式锁
        return redisService.acquireLock(courseId, requestId)
                .flatMap(lockAcquired -> {
                    if (!lockAcquired) {
                        return Mono.error(new RuntimeException("系统繁忙，请稍后重试"));
                    }

                    // 2. 尝试从缓存获取课程信息，如果缓存不存在则从数据库获取
                    return getCourseWithCache(courseId)
                            .flatMap(course -> {
                                // 3. 验证课程状态
                                Long status = course.getStatus();
                                if (status == null || status != 0L) {
                                    String errorMessage;
                                    if (status == null) {
                                        errorMessage = "课程状态异常";
                                    } else if (status == 1L) {
                                        errorMessage = "课程已取消，无法预约";
                                    } else if (status == 2L) {
                                        errorMessage = "课程已结束，无法预约";
                                    } else {
                                        errorMessage = "课程状态不可预约";
                                    }
                                    return releaseLockAndError(courseId, requestId, errorMessage);
                                }

                                // 4. 验证预约时间
                                LocalDateTime now = LocalDateTime.now();
                                LocalDateTime startTime = course.getStartTime();
                                long minutesUntilStart = ChronoUnit.MINUTES.between(now, startTime);
                                if (minutesUntilStart < BOOKING_MIN_ADVANCE_MINUTES) {
                                    return releaseLockAndError(courseId, requestId, 
                                            "需在课程开始前" + BOOKING_MIN_ADVANCE_MINUTES + "分钟预约");
                                }

                                // 5. 验证课程人数是否已满（使用数据库中的current_members字段）
                                if (course.getCurrentMembers() != null && 
                                    course.getMaxMembers() != null &&
                                    course.getCurrentMembers() >= course.getMaxMembers()) {
                                    return releaseLockAndError(courseId, requestId, "课程已满");
                                }

                                // 6. 验证时间冲突（会员是否已预约时间重叠的课程）
                                return bookingRepository.findConflictingBookings(memberId, course.getStartTime(), course.getEndTime())
                                        .collectList()
                                        .flatMap(conflictingBookings -> {
                                            if (!conflictingBookings.isEmpty()) {
                                                StringBuilder errorMsg = new StringBuilder("您已预约的课程与当前课程时间冲突：");
                                                for (GroupCourseBooking conflict : conflictingBookings) {
                                                    errorMsg.append(conflict.getCourseName())
                                                            .append("(")
                                                            .append(conflict.getCourseStartTime())
                                                            .append(" - ")
                                                            .append(conflict.getCourseEndTime())
                                                            .append(") ");
                                                }
                                                return releaseLockAndError(courseId, requestId, errorMsg.toString());
                                            }

                                            // 7. 验证是否已预约
                                            return bookingRepository.findValidBooking(courseId, memberId)
                                                    .flatMap(existingBooking -> {
                                                        return releaseLockAndError(courseId, requestId, "您已预约该课程");
                                                    })
                                                    .switchIfEmpty(
                                                        // 8. 使用Redis原子操作验证课程人数是否已满
                                                        validateAndIncrementBookingCount(courseId, course.getMaxMembers())
                                                            .flatMap(countValid -> {
                                                                if (countValid > course.getMaxMembers()) {
                                                                    return releaseLockAndError(courseId, requestId, "课程已满");
                                                                }

                                                                // 9. 创建预约记录
                                                                GroupCourseBooking booking = new GroupCourseBooking();
                                                                booking.setCourseId(courseId);
                                                                booking.setMemberId(memberId);
                                                                booking.setMemberCardRecordId(memberCardRecordId);
                                                                booking.setBookingTime(LocalDateTime.now());
                                                                booking.setStatus("0"); // 0-已预约

                                                                // 添加课程信息到预约记录
                                                                booking.setCourseName(course.getCourseName());
                                                                booking.setCourseStartTime(course.getStartTime());
                                                                booking.setCourseEndTime(course.getEndTime());
                                                                booking.setLocation(course.getLocation());

                                                                // 10. 使用Saga事务执行预约（包含权益扣减）
                                                                return bookingSagaHandler.executeBooking(booking, memberCardRecordId)
                                                                    .flatMap(savedBooking -> {
                                                                        // 11. 释放锁
                                                                        return redisService.releaseLock(courseId, requestId)
                                                                            .then(Mono.just(savedBooking));
                                                                    })
                                                                    .doOnSuccess(savedBooking -> {
                                                                        logger.info("预约成功：bookingId={}, courseId={}, memberId={}",
                                                                                savedBooking.getId(), courseId, memberId);
                                                                        // 发布预约成功事件
                                                                        bookingReminderEventPublisher.publishBookingSuccessEvent(
                                                                                savedBooking.getId(),
                                                                                savedBooking.getMemberId(),
                                                                                savedBooking.getCourseName(),
                                                                                savedBooking.getCourseStartTime().toString()
                                                                        );
                                                                    })
                                                                    .doOnError(error -> {
                                                                        // 回滚Redis计数
                                                                        redisService.decrementBookingCount(courseId).subscribe();
                                                                        logger.error("预约失败：courseId={}, memberId={}, error={}",
                                                                                courseId, memberId, error.getMessage());
                                                                    });
                                                            })
                                                    );
                                        });
                            })
                            .onErrorResume(error -> {
                                // 发生错误时释放锁
                                redisService.releaseLock(courseId, requestId).subscribe();
                                return Mono.error(error);
                            });
                });
    }

    /**
     * 从缓存或数据库获取课程信息
     */
    private Mono<GroupCourse> getCourseWithCache(Long courseId) {
        return redisService.getCachedCourse(courseId)
                .switchIfEmpty(
                    // 缓存未命中，从数据库查询
                    courseRepository.findByIdAndDeletedAtIsNull(courseId)
                        .switchIfEmpty(Mono.error(new RuntimeException("团课不存在")))
                        .flatMap(course -> {
                            // 将课程信息存入缓存
                            return redisService.cacheCourse(course).then(Mono.just(course));
                        })
                );
    }

    /**
     * 验证并增加预约人数（使用Redis原子操作）
     */
    private Mono<Integer> validateAndIncrementBookingCount(Long courseId, Integer maxMembers) {
        // 先获取当前Redis中的预约计数
        return redisService.getBookingCount(courseId)
                .flatMap(currentCount -> {
                    // 如果Redis中计数为0，可能是首次访问，需要从数据库同步
                    if (currentCount == 0) {
                        // 从数据库查询实际预约人数
                        return bookingRepository.countValidBookings(courseId)
                                .flatMap(dbCount -> {
                                    // 将数据库中的实际预约人数同步到Redis
                                    return redisService.setBookingCount(courseId, dbCount.intValue())
                                            .then(Mono.just(dbCount.intValue()));
                                });
                    }
                    return Mono.just(currentCount);
                })
                // 递增预约计数
                .flatMap(count -> redisService.incrementBookingCount(courseId).map(Long::intValue));
    }

    /**
     * 释放锁并返回错误
     */
    private Mono<GroupCourseBooking> releaseLockAndError(Long courseId, String requestId, String errorMessage) {
        return redisService.releaseLock(courseId, requestId)
                .then(Mono.error(new RuntimeException(errorMessage)));
    }

    @Override
    public Mono<GroupCourseBooking> cancelBooking(Long bookingId, Long memberId) {
        logger.info("开始取消预约：bookingId={}, memberId={}", bookingId, memberId);

        // 生成唯一请求ID用于分布式锁
        String requestId = UUID.randomUUID().toString();

        // 获取锁防止并发取消
        return redisService.acquireLock(bookingId, requestId)
                .flatMap(lockAcquired -> {
                    if (!lockAcquired) {
                        return Mono.error(new RuntimeException("系统繁忙，请稍后重试"));
                    }

                    // 1. 查询预约记录
                    return bookingRepository.findById(bookingId)
                            .switchIfEmpty(Mono.error(new RuntimeException("预约记录不存在")))
                            .flatMap(booking -> {
                                // 2. 验证预约归属
                                if (!booking.getMemberId().equals(memberId)) {
                                    return releaseLockAndError(bookingId, requestId, "无权取消他人预约");
                                }

                                // 3. 验证预约状态
                                String status = booking.getStatus();
                                if (!"0".equals(status)) {
                                    String errorMessage;
                                    if ("1".equals(status)) {
                                        errorMessage = "预约已取消，无需重复取消";
                                    } else if ("2".equals(status)) {
                                        errorMessage = "课程已出席，无法取消";
                                    } else if ("3".equals(status)) {
                                        errorMessage = "已缺席课程，无法取消";
                                    } else {
                                        errorMessage = "预约状态不允许取消";
                                    }
                                    return releaseLockAndError(bookingId, requestId, errorMessage);
                                }

                                // 4. 验证取消时间
                                LocalDateTime now = LocalDateTime.now();
                                LocalDateTime startTime = booking.getCourseStartTime();
                                if (startTime == null) {
                                    return releaseLockAndError(bookingId, requestId, "课程开始时间为空，无法取消预约");
                                }
                                long hoursUntilStart = ChronoUnit.HOURS.between(now, startTime);
                                if (hoursUntilStart < CANCEL_MIN_ADVANCE_HOURS) {
                                    return releaseLockAndError(bookingId, requestId, 
                                            "需在课程开始前" + CANCEL_MIN_ADVANCE_HOURS + "小时取消");
                                }

                                // 5. 使用Saga事务执行取消预约（包含权益恢复）
                                return bookingSagaHandler.executeCancelBooking(bookingId, booking.getCourseId(), booking.getMemberCardRecordId(), memberId)
                                    .flatMap(updatedBooking -> {
                                        // 6. 释放锁
                                        return redisService.releaseLock(bookingId, requestId)
                                            .then(Mono.just(updatedBooking));
                                    })
                                    .doOnSuccess(updatedBooking -> {
                                        logger.info("取消预约成功：bookingId={}, memberId={}", bookingId, memberId);
                                        // 发布预约取消事件
                                        bookingReminderEventPublisher.publishBookingCancelEvent(
                                                updatedBooking.getId(),
                                                updatedBooking.getMemberId(),
                                                updatedBooking.getCourseName()
                                        );
                                    })
                                    .doOnError(error -> {
                                        logger.error("取消预约失败：bookingId={}, memberId={}, error={}",
                                                bookingId, memberId, error.getMessage());
                                    });
                            })
                            .onErrorResume(error -> {
                                redisService.releaseLock(bookingId, requestId).subscribe();
                                return Mono.error(error);
                            });
                });
    }

    @Override
    public Flux<GroupCourseBooking> getBookingsByMemberId(Long memberId) {
        logger.debug("查询会员预约记录：memberId={}", memberId);
        return bookingRepository.findByMemberId(memberId)
                .doOnComplete(() -> logger.debug("查询完成：memberId={}", memberId));
    }

    @Override
    public Mono<GroupCourseBooking> getBookingById(Long bookingId) {
        logger.debug("查询预约详情：bookingId={}", bookingId);
        return bookingRepository.findById(bookingId);
    }

    @Override
    public Flux<GroupCourseBooking> getBookingsByCourseId(Long courseId) {
        logger.debug("查询课程预约记录：courseId={}", courseId);
        return bookingRepository.findByCourseId(courseId)
                .doOnComplete(() -> logger.debug("查询完成：courseId={}", courseId));
    }

    @Override
    public Mono<Integer> processAbsentMembers() {
        logger.info("开始处理已开始课程但未到场会员的预约记录");
        
        return bookingRepository.findAbsentMembers()
                .collectList()
                .flatMap(absentBookings -> {
                    if (absentBookings.isEmpty()) {
                        logger.info("没有需要处理的缺席会员记录");
                        return Mono.just(0);
                    }
                    
                    logger.info("发现 {} 条需要更新为缺席状态的预约记录", absentBookings.size());
                    
                    // 批量更新状态为缺席
                    return Flux.fromIterable(absentBookings)
                            .flatMap(booking -> {
                                logger.debug("更新预约记录为缺席状态：bookingId={}, memberId={}, courseId={}", 
                                        booking.getId(), booking.getMemberId(), booking.getCourseId());
                                return bookingRepository.updateToAbsent(booking.getId());
                            })
                            .reduce(Integer::sum)
                            .defaultIfEmpty(0)
                            .doOnSuccess(count -> {
                                logger.info("成功更新 {} 条预约记录为缺席状态", count);
                            })
                            .doOnError(error -> {
                                logger.error("处理缺席会员记录时发生错误：{}", error.getMessage(), error);
                            });
                });
    }
}
