package cn.novalon.gym.manage.groupcourse.handler;

import cn.novalon.gym.manage.groupcourse.domain.GroupCourseBooking;
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseBookingRepository;
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
import cn.novalon.gym.manage.groupcourse.service.impl.GroupCourseRedisService;
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.MemberCardRepository;
import cn.novalon.gym.manage.member.service.IMemberCardRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

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

/**
 * 预约 Saga 处理器
 *
 * 实现预约和取消预约的分布式事务处理
 *
 * @author 张翔
 * @date 2026-06-02
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class BookingSagaHandler {

    private final IGroupCourseBookingRepository bookingRepository;
    private final IGroupCourseRepository courseRepository;
    private final IMemberCardRecordService memberCardRecordService;
    private final MemberCardRepository memberCardRepository;
    private final GroupCourseRedisService redisService;

    private static final double DEFAULT_GROUP_COURSE_PRICE = 50.0;

    /**
     * 执行预约事务
     *
     * 步骤：
     * 1. 保存预约记录
     * 2. 扣减会员卡权益
     * 3. 更新课程当前人数
     */
    public Mono<GroupCourseBooking> executeBooking(GroupCourseBooking booking, Long recordId) {
        List<SagaStep> steps = new ArrayList<>();
        List<SagaStep> rollbackSteps = new ArrayList<>();

        // 步骤1：保存预约记录
        SagaStep step1 = new SagaStep(
                "保存预约记录",
                saveBooking(booking),
                Mono.defer(() -> deleteBooking(booking.getId()))
        );
        steps.add(step1);
        rollbackSteps.add(0, step1);

        // 步骤2：扣减会员卡权益（根据卡类型决定扣除次数还是金额）
        SagaStep step2 = new SagaStep(
                "扣减会员卡权益",
                deductCardUsageByCardType(booking.getMemberId(), recordId),
                Mono.defer(() -> restoreCardUsageByCardType(booking.getMemberId(), recordId))
        );
        steps.add(step2);
        rollbackSteps.add(0, step2);

        // 步骤3：更新课程当前人数
        SagaStep step3 = new SagaStep(
                "更新课程当前人数",
                incrementCourseCurrentMembers(booking.getCourseId()),
                Mono.defer(() -> decrementCourseCurrentMembers(booking.getCourseId()))
        );
        steps.add(step3);
        rollbackSteps.add(0, step3);

        // 步骤4：更新Redis预约计数
        SagaStep step4 = new SagaStep(
                "更新Redis预约计数",
                incrementRedisBookingCount(booking.getCourseId()),
                Mono.defer(() -> decrementRedisBookingCount(booking.getCourseId()))
        );
        steps.add(step4);
        rollbackSteps.add(0, step4);

        return executeSaga(steps, rollbackSteps)
                .then(Mono.just(booking));
    }

    /**
     * 根据会员卡类型扣减权益
     */
    private Mono<Void> deductCardUsageByCardType(Long memberId, Long recordId) {
        return memberCardRecordService.findById(recordId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在")))
                .flatMap(record -> {
                    // 验证会员卡归属
                    if (!record.getMemberId().equals(memberId)) {
                        return Mono.error(new RuntimeException("会员卡不归属当前用户"));
                    }

                    return memberCardRepository.findById(record.getMemberCardId())
                            .flatMap(card -> {
                                MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());

                                switch (cardType) {
                                    case COUNT_CARD:
                                        // 次卡扣除次数
                                        return deductCardUsage(recordId, 1, 0.0);
                                    case STORED_VALUE_CARD:
                                        // 储值卡扣除金额
                                        return deductCardUsage(recordId, 0, DEFAULT_GROUP_COURSE_PRICE);
                                    case TIME_CARD:
                                        // 时长卡不扣除，但需验证有效期
                                        return validateTimeCard(record);
                                    default:
                                        return Mono.error(new RuntimeException("不支持的会员卡类型: " + cardType));
                                }
                            });
                });
    }

    /**
     * 验证时长卡有效期
     * 
     * @param record 会员卡记录
     * @return 验证通过返回Mono.empty()，否则返回Mono.error()
     */
    private Mono<Void> validateTimeCard(MemberCardRecord record) {
        // 验证会员卡状态
        cn.novalon.gym.manage.member.enums.MemberCardRecordStatus status = record.getStatus();
        if (status != cn.novalon.gym.manage.member.enums.MemberCardRecordStatus.ACTIVE) {
            return Mono.error(new RuntimeException("会员卡状态无效，当前状态: " + (status != null ? status.getDesc() : "未知")));
        }
        
        // 验证有效期
        java.time.LocalDateTime expireTime = record.getExpireTime();
        if (expireTime == null) {
            return Mono.error(new RuntimeException("会员卡有效期未设置"));
        }
        if (expireTime.isBefore(java.time.LocalDateTime.now())) {
            return Mono.error(new RuntimeException("会员卡已过期，有效期至: " + expireTime));
        }
        
        log.debug("时长卡验证通过: recordId={}, expireTime={}", record.getId(), expireTime);
        return Mono.empty();
    }

    /**
     * 根据会员卡类型恢复权益
     */
    private Mono<Void> restoreCardUsageByCardType(Long memberId, Long recordId) {
        return memberCardRecordService.findById(recordId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在，无法恢复权益")))
                .flatMap(record -> {
                    // 验证会员卡归属（memberId为null时跳过验证）
                    if (memberId != null && !record.getMemberId().equals(memberId)) {
                        return Mono.error(new RuntimeException("会员卡不归属当前用户"));
                    }

                    return memberCardRepository.findById(record.getMemberCardId())
                            .flatMap(card -> {
                                MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());

                                switch (cardType) {
                                    case COUNT_CARD:
                                        return restoreCardUsage(recordId, 1, 0.0);
                                    case STORED_VALUE_CARD:
                                        return restoreCardUsage(recordId, 0, DEFAULT_GROUP_COURSE_PRICE);
                                    case TIME_CARD:
                                        return Mono.empty();
                                    default:
                                        return Mono.error(new RuntimeException("不支持的会员卡类型: " + cardType));
                                }
                            });
                });
    }

    /**
     * 执行取消预约事务
     */
    public Mono<GroupCourseBooking> executeCancelBooking(Long bookingId, Long courseId, Long recordId, Long memberId) {
        List<SagaStep> steps = new ArrayList<>();
        List<SagaStep> rollbackSteps = new ArrayList<>();

        // 步骤1：更新预约状态为已取消
        SagaStep step1 = new SagaStep(
                "更新预约状态为已取消",
                updateBookingStatus(bookingId, "1"),
                Mono.defer(() -> updateBookingStatus(bookingId, "0"))
        );
        steps.add(step1);
        rollbackSteps.add(0, step1);

        // 步骤2：恢复会员卡权益
        SagaStep step2 = new SagaStep(
                "恢复会员卡权益",
                restoreCardUsageByCardType(memberId, recordId),
                Mono.defer(() -> deductCardUsageByCardType(memberId, recordId))
        );
        steps.add(step2);
        rollbackSteps.add(0, step2);

        // 步骤3：减少课程当前人数
        SagaStep step3 = new SagaStep(
                "减少课程当前人数",
                decrementCourseCurrentMembers(courseId),
                Mono.defer(() -> incrementCourseCurrentMembers(courseId))
        );
        steps.add(step3);
        rollbackSteps.add(0, step3);

        // 步骤4：更新Redis预约计数
        SagaStep step4 = new SagaStep(
                "更新Redis预约计数",
                decrementRedisBookingCount(courseId),
                Mono.defer(() -> incrementRedisBookingCount(courseId))
        );
        steps.add(step4);
        rollbackSteps.add(0, step4);

        return executeSaga(steps, rollbackSteps)
                .then(bookingRepository.findById(bookingId));
    }

    private Mono<Void> updateBookingStatus(Long bookingId, String status) {
        return bookingRepository.updateStatus(bookingId, status)
                .flatMap(rows -> {
                    if (rows == 0) {
                        return Mono.error(new RuntimeException("更新预约状态失败"));
                    }
                    return Mono.empty();
                });
    }

    private Mono<Void> saveBooking(GroupCourseBooking booking) {
        return bookingRepository.save(booking)
                .flatMap(saved -> {
                    if (saved.getId() == null) {
                        return Mono.error(new RuntimeException("保存预约记录失败"));
                    }
                    booking.setId(saved.getId());
                    return Mono.empty();
                });
    }

    private Mono<Void> deleteBooking(Long bookingId) {
        return bookingRepository.deleteById(bookingId);
    }

    private Mono<Void> deductCardUsage(Long recordId, Integer deductTimes, Double deductAmount) {
        if (deductTimes == 0 && deductAmount == 0.0) {
            return Mono.empty();
        }

        return memberCardRecordService.findById(recordId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在")))
                .flatMap(record -> {
                    cn.novalon.gym.manage.member.enums.MemberCardRecordStatus status = record.getStatus();
                    if (status != cn.novalon.gym.manage.member.enums.MemberCardRecordStatus.ACTIVE) {
                        return Mono.error(new RuntimeException("会员卡状态无效，当前状态: " + (status != null ? status.getDesc() : "未知")));
                    }
                    java.time.LocalDateTime expireTime = record.getExpireTime();
                    if (expireTime != null && expireTime.isBefore(java.time.LocalDateTime.now())) {
                        return Mono.error(new RuntimeException("会员卡已过期"));
                    }
                    if (record.getRemainingTimes() != null && deductTimes > 0 && record.getRemainingTimes() < deductTimes) {
                        return Mono.error(new RuntimeException("会员卡剩余次数不足，当前剩余: " + record.getRemainingTimes() + "次"));
                    }
                    if (record.getRemainingAmount() != null && deductAmount > 0 && record.getRemainingAmount() < deductAmount) {
                        return Mono.error(new RuntimeException("会员卡余额不足，当前剩余: " + record.getRemainingAmount()));
                    }
                    return memberCardRecordService.deductUsage(recordId, deductTimes, deductAmount)
                            .flatMap(rows -> {
                                if (rows == 0) {
                                    return Mono.error(new RuntimeException("扣减会员卡权益失败，请重试"));
                                }
                                return Mono.empty();
                            });
                });
    }

    private Mono<Void> restoreCardUsage(Long recordId, Integer addTimes, Double addAmount) {
        if (addTimes == 0 && addAmount == 0.0) {
            return Mono.empty();
        }

        return memberCardRecordService.findById(recordId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在，无法恢复权益")))
                .flatMap(record -> {
                    // 使用当前记录的过期时间，避免清空过期时间
                    return memberCardRecordService.renewCard(recordId, addTimes, addAmount, record.getExpireTime())
                            .flatMap(rows -> {
                                if (rows == 0) {
                                    return Mono.error(new RuntimeException("恢复会员卡权益失败，请重试"));
                                }
                                return Mono.empty();
                            });
                });
    }

    private Mono<Void> incrementCourseCurrentMembers(Long courseId) {
        return courseRepository.updateCurrentMembers(courseId, 1).then();
    }

    private Mono<Void> decrementCourseCurrentMembers(Long courseId) {
        return courseRepository.updateCurrentMembers(courseId, -1).then();
    }

    private Mono<Void> incrementRedisBookingCount(Long courseId) {
        return redisService.incrementBookingCount(courseId).then();
    }

    private Mono<Void> decrementRedisBookingCount(Long courseId) {
        return redisService.decrementBookingCount(courseId).then();
    }

    private Mono<Void> executeSaga(List<SagaStep> steps, List<SagaStep> rollbackSteps) {
        List<SagaStep> completedSteps = new ArrayList<>();
        return executeStep(steps, 0, completedSteps);
    }

    private Mono<Void> executeStep(List<SagaStep> steps, int index, List<SagaStep> completedSteps) {
        if (index >= steps.size()) {
            return Mono.empty();
        }

        SagaStep currentStep = steps.get(index);

        return currentStep.operation()
                .doOnSuccess(unused -> completedSteps.add(currentStep))
                .then(Mono.defer(() -> executeStep(steps, index + 1, completedSteps)))
                .onErrorResume(error -> {
                    log.error("Saga步骤执行失败: step={}, error={}", currentStep.description(), error.getMessage());
                    return rollbackCompletedSteps(completedSteps).then(Mono.error(error));
                });
    }

    private Mono<Void> rollbackCompletedSteps(List<SagaStep> completedSteps) {
        if (completedSteps.isEmpty()) {
            return Mono.empty();
        }
        
        // 反转已完成的步骤，按逆序回滚
        List<SagaStep> reversed = new ArrayList<>(completedSteps);
        Collections.reverse(reversed);
        
        return rollbackSteps(reversed, 0);
    }

    private Mono<Void> rollbackSteps(List<SagaStep> rollbackSteps, int index) {
        if (index >= rollbackSteps.size()) {
            return Mono.empty();
        }

        SagaStep currentStep = rollbackSteps.get(index);

        return currentStep.rollbackOperation()
                .then(Mono.defer(() -> rollbackSteps(rollbackSteps, index + 1)))
                .doOnError(error -> log.error("Saga回滚失败: step={}, error={}", currentStep.description(), error.getMessage()))
                .onErrorResume(e -> Mono.empty());
    }

    private record SagaStep(String description, Mono<Void> operation, Mono<Void> rollbackOperation) {}
}
